I have been trying this for a week. And I have crawled every article available but their implementations or examples fall short or stop at the steps of Espresso Tests.
My Android Application follows MVP architecture (And is in Java)
Scenario: [Giving just one example]
I have a HomeActivity
which gets a HomePresenter
using Dagger2. (Provides method in the HomeModule
exposed through a void inject(HomeActivity activity)
in the HomeComponent
.
In my espressoTest for HomeActivity
I would like to inject a mockpresent.
I Have not exposed this dependencies inside an AppModule
through an AppComponent
. which most examples on the net do (So they just create a new testApplication and then do the needfull)
I do not want to use the productFlavours way of injecting or providing mockclasses as it doesnt give me control over the Mockito.when
methods.
So basically. I would like to inject a mockpresenter wherein i can do whatever Mockito.when()
s on it for the sake of my unit tests in espresso.
HomeComponent
@HomeScope
@Component(modules = HomeModule.class,dependencies = AppComponent.class)
public interface HomeComponent {
void inject(HomeActivity activity);
}
HomeModule
@Module
public class HomeModule {
private final IHomeContract.View view;
public HomeModule(IHomeContract.View view) {
this.view = view;
}
@Provides
@HomeScope
public IHomeContract.Presenter presenter(FlowsRepository flowsRepository, UserRepository userRepository, LoanRepository loanRepository) {
return new HomePresenter(view, flowsRepository, userRepository, loanRepository);
}
}
AppComponent
@Component(modules = {AppModule.class,RepositoryModule.class})
@AppScope
public interface AppComponent {
void inject(App app);
FlowsRepository flowRepository();
LoanRepository loanRepository();
UserRepository userRepository();
}
AppModule
@Module
public class AppModule {
private Context appContext;
public AppModule(@NonNull Context context) {
this.appContext = context;
}
@Provides
@AppScope
public Context context() {
return appContext;
}
}
App
component = DaggerAppComponent.builder()
.appModule(new AppModule(this))
.build();
component.inject(this);
HomeActivity
HomeComponent component = DaggerHomeComponent.builder()
.appComponent(((App) getApplication()).getComponent())
.homeModule(new HomeModule(this))
.build();
Once again. In my tests (espresso) i would like to inject a mockedHomePresenter the set by Mockito. So I can just unit test my views.
The key point in resolving the problem is to have such a Dagger Module that provides a mock Presenter in HomeActivity
's instrumented test instead of the "real" one.
For this the following 2 extra actions need to be done (you might also want to see an example).
I'll use Kotlin in the example below.
Define the delegate interface:
interface HomeComponentBuilder {
fun build(view: IHomeContract.View): HomeComponent
}
Move the HomeComponent
initialisation from HomeActivity
to the delegate implementation:
class HomeComponentBuilderImpl constructor(private val app: App) : HomeComponentBuilder {
override fun build(view: IHomeContract.View): HomeComponent =
DaggerHomeComponent.builder()
.homeModule(HomeModule(view))
.build()
}
Make the delegate be in application "scope" so that you could interchange its implementation for instrumented tests:
interface App {
val homeComponentBuilder: HomeComponentBuilder
...
}
App
implementation should now contain
class AppImpl : Application(), App {
override val homeComponentBuilder: HomeComponentBuilder by lazy {
HomeComponentBuilderImpl(this@AppImpl)
}
...
}
Component initialisation in HomeActivity
looks as follows:
(application as App)
.homeComponentBuilder
.build(this)
.inject(this)
For instrumented testing create TestHomeComponent
that extends HomeComponent
:
@HomeScope
@Component(modules = [TestHomeModule::class])
interface TestHomeComponent : HomeComponent
where TestHomeModule
provides a mock Presenter
@Module
class TestHomeModule {
@Provides
fun providePresenter(): IHomeContract.Presenter = mock()
}
What's left to do is to make a test delegate implementation
class TestHomeComponentBuilderImpl : HomeComponentBuilder {
override fun build(view: IHomeContract.View): HomeComponent =
DaggerTestHomeComponent.builder()
.testTestHomeModule(TestHomeModule())
.build()
}
and initialise it in TestAppImpl
class TestAppImpl : Application(), App {
override val homeComponentBuilder: HomeComponentBuilder by lazy {
TestHomeComponentBuilderImpl()
}
...
}
The rest is standard. Create a custom AndroidJUnitRunner
that uses TestAppImpl
:
class TestAppRunner : AndroidJUnitRunner() {
override fun newApplication(cl: ClassLoader?, className: String?, context: Context?): Application = Instrumentation.newApplication(TestAppImpl::class.java, context)
}
and add it to app
module build.gradle
defaultConfig {
testInstrumentationRunner "your.package.TestAppRunner"
...
}
Usage example:
@RunWith(AndroidJUnit4::class)
class HomeActivityTest {
private lateinit var mockPresenter: IHomeContract.Presenter
@get:Rule
val activityRule = ActivityTestRule(HomeActivity::class.java)
@Before
fun setUp() {
mockPresenter = activityRule.activity.presenter
}
@Test
fun activity_onCreate_presenter_should_onViewCreated() {
verify(mockPresenter).someMethod()
}
}
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