As advised in Dagger documentation, for unit testing we don't have to involve Dagger at all, and for the provided example it makes sense:
class ThingDoer {
private final ThingGetter getter;
private final ThingPutter putter;
@Inject ThingDoer(ThingGetter getter, ThingPutter putter) {
this.getter = getter;
this.putter = putter;
}
String doTheThing(int howManyTimes) { /* … */ }
}
With this class structure it is simple to unit test, just mock the getter
and putter
, pass them as constructor arguments, instruct mockito what to return when interacting with any of these objects, and then make assertions on doTheThing(...)
.
Where I am struggling at testing is when I have to unit test a class with a structure similar to this:
class ThingDoer {
@Inject
ThingGetter getter;
@Inject
ThingPutter putter;
@Inject
ThingMaker maker;
@Inject
// other 10 objects
public ThingDoer() {
App.getThingComponent().inject(this);
}
String doTheThing(int howManyTimes) { /* … */ }
}
As you can see, I am no longer using constructor injection, but field injection instead. The main reason is not have too many parameters in the constructor.
Is there a way to inject mock dependencies in ThingDoer
when all its dependencies are provided using field injections?
Field Injection:- Field which participates in the injection is called field injection. Dagger doesn't support Injection in private fields and the final field. Because the private field does not bind with our component and the final can't change its value.
x: It is an adaptation of an earlier version created by Square and now maintained by Google. Dagger2 is compile time Dependency injection framework that generates code to connect dependencies at compile time.
Subcomponent are components that is like an extension to its parent component. Partition the dependencies into different compartments. Avoid the parent component to have too many dependencies bound to it. Subcomponent and its parent component have different scope (of lifespan).
The main objective of unit testing is to isolate written code to test and determine if it works as intended. Unit testing is an important step in the development process, because if done correctly, it can help detect early flaws in code which may be more difficult to find in later testing stages.
For field injection, you can create a component and a module which are used in unit test.
Suppose you have the unit test class ThingDoerTest
, you can make the component injects dependencies to ThingDoerTest
instead ThingDoer
and the module provides the mock object instead real object.
In my project, HomeActivity
has a field injection HomePresenter
. Following code are some snippets. Hope the code can give you some idea.
@RunWith(AndroidJUnit4.class)
public class HomeActivityTest implements ActivityLifecycleInjector<HomeActivity>{
@Rule
public InjectorActivityTestRule<HomeActivity> activityTestRule = new InjectorActivityTestRule<>(HomeActivity.class, this);
@Inject
public HomePresenter mockHomePresenter;
@Override
public void beforeOnCreate(HomeActivity homeActivity) {
Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
MyApplication myApplication = (MyApplication) instrumentation.getTargetContext().getApplicationContext();
TestHomeComponent testHomeComponent = DaggerHomeActivityTest_TestHomeComponent.builder()
.appComponent(myApplication.getAppComponent())
.mockHomeModule(new MockHomeModule())
.build();
testHomeComponent.inject(HomeActivityTest.this);
homeActivity.setHomeComponent(testHomeComponent);
}
@Test
public void testOnCreate() throws Exception {
verify(mockHomePresenter).start();
}
@ActivityScope
@Component(
dependencies = {
AppComponent.class
},
modules = {
MockHomeModule.class
}
)
public interface TestHomeComponent extends HomeComponent {
void inject(HomeActivityTest homeActivityTest);
}
@Module
public class MockHomeModule {
@ActivityScope
@Provides
public HomePresenter provideHomePresenter() {
return mock(HomePresenter.class);
}
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With