MockWebServer + Dagger2 with style😎🍸

Fernando Prieto
3 min readNov 19, 2019

--

The aim of this post is to guide people during the MockWebServer setup, without breaking any Clean Architecture principle, compromising the production code or adding boiler plate.

So far, I haven’t found a single example on the web which hasn’t broken at least one of these three principles. Until now. I’m going to set out my own example, which doesn’t break any of the above principles, and which will make coding easier for other developers who want to implement a similar approach using Dagger2 and MockWebServer.

Firstly, I will explain why the first approach was ‘not clean’, and why I decided to go further until I found the solution.

A month ago I built a Kotlin sample project using MVVM, Clean Architecture, LiveData and RxJava. In terms of testing I used JUnit, Mockito and Espresso (with Robot pattern). I decided to make it modular, in order to abstract the architecture layers , and I also crated a navigation module.

Let’s start configuring our project to support MockWebServer

The first step, as always, was to add the dependency to the build.gradle

androidTestImplementation 'com.squareup.okhttp3:mockwebserver:4.2.1'

Pre-requisites

In order to make the Url injectable and to potentially be replaced by the one used through MockWebServer, I defined a @Named String

AppModule.kt

@Provides
@Named("API_URL")
fun provideApiUrl() = "https://api.icndb.com/"

NetworkModule.kt

@Provides
@Singleton
fun provideRetrofitBuilder(
rxJavaCallAdapterFactory: RxJava2CallAdapterFactory,
gsonConverterFactory: GsonConverterFactory,
@Named("API_URL") apiUrl: String
): Retrofit.Builder = Retrofit.Builder()
.baseUrl(apiUrl)
.addConverterFactory(gsonConverterFactory)
.addCallAdapterFactory(rxJavaCallAdapterFactory)

At this point, I thought all that was needed was a TestModule with the MockWebServer Url and PORT when the tests were run.

@Module
class TestAppModule {
@Provides
@Named("API_URL")
fun provideApiUrl() = "http://127.0.0.1:${BuildConfig.PORT}"
}

Easy right? However, I instead needed to use a TestAppModule within a different AppComponent, which includes all of the modules declared. I then needed to replace the AppModule for the TestAppModule.

Realisation 💡

OK, ok, stop coding! It was here where I realised I was implementing something incorrectly:

  • Two AppComponents, one for Test and another for the App itself
  • A new DaggerApplication (TestApplication) to declare the applicationInjector, thus the DaggerTestAppComponent.
  • Mixing Test Dependency Injection classes with the code base, instead of having all of the test related classes isolated in androidTest package.

Let’s start doing things correctly 👊🏼

Shout out to my colleague Alex Facciorusso , who dug into the Dagger mechanisms and found out a way to create DaggerAppComponent builders through Kotlin DSL. Thanks to his findings, I was able to invoke the original AppComponent and inject the NetworkModule with a different url.

Let’s check the TestConfigurationBuilder with the DSLs and Dagger injections:

TestConfigurationBuilder.kt

If a url has not been provided as a parameter, retrofit will use the original endpoint

Let’s have a look at the UI Tests

Basic configuration

I added the usual configuration for MockWebServer

private val mockWebServer = MockWebServer()

@Before
fun setup() {
mockWebServer.start(BuildConfig.PORT)
}

@After
fun teardown() {
mockWebServer.shutdown()
}

I defined an ErrorDispatcher and SuccessDispatcher, which deliver responses from json files allocated in assets folder.

In-Test configuration 🔎

In order to set the base Url before the activity gets launched, I had to call injectTestConfiguration before launchActivity (androidx.test.core.app)

Once the Activity has been launched, MockWebServer will be able to select either ErrorDispatcher or SuccessDispatcher.

The final step was to add the assertions that I wanted to test. In my project I decided to use the Espresso Robot Pattern, which makes the code more readable and clean.

@Test
fun openRandomJokeDialog() {
injectTestConfiguration {
testBaseUrl()
}
launchActivity<MainActivity>()
mockWebServer.dispatcher = SuccessDispatcher()

DashboardFragmentRobot()
.assertButtonRandomJokeDisplayed()
.clickButtonRandomJoke()
.assertDialogViewRandomJokeDisplayed()
}

The entire Sample Project

MMVM-Modularized

If you liked this article, make sure to follow me on github for new interesting content.
Massive thanks! 🙏🏼

--

--