How do you override a module/dependency in a unit test with Dagger 2.0?
Probably this is more a workaround that proper support for test module overriding, but it allows to override production modules with test one. The code snippets below shows simple case when you have just one component and one module, but this should work for any scenario. It requires a lot of boilerplate and code repetition so be aware of this. I'm sure there'll be a better way to achieve this in the future.
I've also created a project with examples for Espresso and Robolectric. This answer is based on code contained in the project.
The solution requires two things:
- provide additional setter for
@Component
- test component must extend the production component
Assume we've simple Application
like below:
public class App extends Application {
private AppComponent mAppComponent;
@Override
public void onCreate() {
super.onCreate();
mAppComponent = DaggerApp_AppComponent.create();
}
public AppComponent component() {
return mAppComponent;
}
@Singleton
@Component(modules = StringHolderModule.class)
public interface AppComponent {
void inject(MainActivity activity);
}
@Module
public static class StringHolderModule {
@Provides
StringHolder provideString() {
return new StringHolder("Release string");
}
}
}
We've to add additional method to App
class. This allows us to replace the production component.
/**
* Visible only for testing purposes.
*/
// @VisibleForTesting
public void setTestComponent(AppComponent appComponent) {
mAppComponent = appComponent;
}
As you can see the StringHolder
object contains "Release string" value. This object is injected to the MainActivity
.
public class MainActivity extends ActionBarActivity {
@Inject
StringHolder mStringHolder;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
((App) getApplication()).component().inject(this);
}
}
In our tests we want to provide StringHolder
with "Test string". We've to set the test component in App
class before the MainActivity
is created - because StringHolder
is injected in the onCreate
callback.
In Dagger v2.0.0 components can extend other interfaces. We can leverage this to create our TestAppComponent
which extends AppComponent
.
@Component(modules = TestStringHolderModule.class)
interface TestAppComponent extends AppComponent {
}
Now we're able to define our test modules e.g. TestStringHolderModule
. The last step is to set the test component using previously added setter method in App
class. It's important to do this before the activity is created.
((App) application).setTestComponent(mTestAppComponent);
Espresso
For Espresso I've created custom ActivityTestRule
which allows to swap the component before the activity is created. You can find code for DaggerActivityTestRule
here.
Sample test with Espresso:
@RunWith(AndroidJUnit4.class)
@LargeTest
public class MainActivityEspressoTest {
public static final String TEST_STRING = "Test string";
private TestAppComponent mTestAppComponent;
@Rule
public ActivityTestRule<MainActivity> mActivityRule =
new DaggerActivityTestRule<>(MainActivity.class, new OnBeforeActivityLaunchedListener<MainActivity>() {
@Override
public void beforeActivityLaunched(@NonNull Application application, @NonNull MainActivity activity) {
mTestAppComponent = DaggerMainActivityEspressoTest_TestAppComponent.create();
((App) application).setTestComponent(mTestAppComponent);
}
});
@Component(modules = TestStringHolderModule.class)
interface TestAppComponent extends AppComponent {
}
@Module
static class TestStringHolderModule {
@Provides
StringHolder provideString() {
return new StringHolder(TEST_STRING);
}
}
@Test
public void checkSomething() {
// given
...
// when
onView(...)
// then
onView(...)
.check(...);
}
}
Robolectric
It's much easier with Robolectric thanks to the RuntimeEnvironment.application
.
Sample test with Robolectric:
@RunWith(RobolectricGradleTestRunner.class)
@Config(emulateSdk = 21, reportSdk = 21, constants = BuildConfig.class)
public class MainActivityRobolectricTest {
public static final String TEST_STRING = "Test string";
@Before
public void setTestComponent() {
AppComponent appComponent = DaggerMainActivityRobolectricTest_TestAppComponent.create();
((App) RuntimeEnvironment.application).setTestComponent(appComponent);
}
@Component(modules = TestStringHolderModule.class)
interface TestAppComponent extends AppComponent {
}
@Module
static class TestStringHolderModule {
@Provides
StringHolder provideString() {
return new StringHolder(TEST_STRING);
}
}
@Test
public void checkSomething() {
// given
MainActivity mainActivity = Robolectric.setupActivity(MainActivity.class);
// when
...
// then
assertThat(...)
}
}
Override dependencies in Dagger Module
You're binding each as Navigator. I believe you need to use a different return type on your shadowed binding.
Alternatively, you could try something with Qualifiers. Defining a custom qualifier is easy; you should be able to find examples online. I'd share one, but I'm on my phone right now.
This answer has been accepted, so I'd like to add some code to make it more "complete". Here's an example of a custom "Qualifier" (Kotlin)
import javax.inject.Qualifier
@Qualifier
@Retention(AnnotationRetention.RUNTIME)
annotation class DelayQualifier
Usage:
@Module object {
@Provides @DelayQualifier @JvmStatic
fun provideDelay(): Long = if (BuildConfig.DEBUG) 1L else 3L
}
@ActivityScoped
class SignupViewModelFactory @Inject constructor(
@param:DelayQualifier private val delay: Long
) : ViewModelProvider.Factory { ... }
This is the only Long
I'm currently injecting in my project, so I don't need the qualifier. But if I decide I want more Long
s, I'll regret not qualifying this one.
Migrating to Dagger 2.12 and test module overrides
I ended up solving it by simply not annotating the TestAppModule
with @Module
. This achieves partial mocking.
public class TestAppModule extends AppModule {
@Override
public UserDao provideUserDao() {
return mock(UserDao.class);
}
}
Android Unit Tests with Dagger 2
This is currently impossible with Dagger 2 (as of v2.0.0) without some workarounds. You can read about it here.
More about possible workarounds:
How do you override a module/dependency in a unit test with Dagger 2.0?
Creating test dependencies when using Dagger2
Related Topics
Android: HTML in Textview with Link Clickable
What Is The Easiest Way to Use Svg Images in Android
Activity.Finish() Called But Activity Stays Loaded in Memory
How to Save Image Taken from Camera and Show It to Listview - Crashes with "Illegalstateexception"
Save Webview Content to Android Storage and Load It
Find the Physical Address of Exception Vector Table from Kernel Module
Android: How to Return Async JSONobject from Method Using Volley
Fragment View in Viewpager Is Not Restored When Resuming
In HTML5, How to Keep an Android Device's Screen On
How to Get Raw Preview Data from Camera Object at Least 15 Frames Per Second in Android
Java.Lang.Nosuchmethoderror: No Static Method Setonapplywindowinsetslistener
Is There a Workaround for The Android Browser Bug with CSS-Position and Clickable Areas
Android Imageview Change Tint to Simulate Button Click
Adding a Progress Dialog in a Webview
Eclipse Indexer Errors When Using Stl with Android Ndk
Input Field Hidden When Soft Keyboard Appears in Phonegap
How to Create a Floating Action Button (Fab) in Android, Using Appcompat V21