Mocking in In-App Purchase UI Tests — Mockito, Dagger2 and Espresso

Testing In-App Purchase (IAP) flow is especially difficult — completing the flow produces side effects that are hard to reverse, e.g. upgrading a user or making a purchase, so it makes sense to mock these calls even in a UI test. In this article we will create Espresso UI Instrumented tests for Android that will proceed through the IAP, mocking the responses from Google Billing and our own user database.

The stack

The process

Create directory your/folder/location/src/androidTest. It should be on the same level as src/main.

Within this directory, mirror your src/main/ directory structure.

  1. Create a test DaggerComponent in /androidTest/com/domain/.../dagger, one that will supply our mocked dependencies.
  2. Create a test DaggerApplication, one that will receive our mocked modules.
  3. Create a test runner that uses our test application.
  4. Write tests! Run with the test runner.

Step one — Dagger Component

A quick overview of Dagger2. In order to provide dependencies:

  1. Specify module classes that provide objects of a certain class type. The class is annotated with @Module and methods are annotated with @Provides. The names don’t matter and are just convention. The return type of the method is what’s used in the graph. For tests, it is useful to annotate all @Provides with @Singleton
@Module
class MockAppModule {
   @Provides
@Singleton
fun provideUserManager = UserManager()
   @Provides
@Singleton
fun provideApi = mock(Api::class.java)
}

2. Specify a Component; An adapter between your modules and your injected fields. @Components can be built and injected into different contexts to provide injected fields.

@Component(modules = arrayOf(MockAppModule::class))
@Singleton
interface MockComponent : AndroidInjector<TestApplication> {
    fun inject(tests: ApplicationUITests)
}

There are 2 new additions we will ignore for the time being, TestApplication and the inject method.

Step Two — Create a test application

Our tests have a different dependency graph than our production application. Create a test application that uses our test dependency graph.

class TestApplication : DaggerApplication() {

lateinit var component: TestComponent

override fun applicationInjector(): AndroidInjector<out DaggerApplication> = DaggerTestComponent
.builder()
.build()
.also { component = it }
}

For Dagger to recognize that the annotations in TestComponent needs to be generated into a TestComponent, add to your build.gradle

kaptAndroidTest "com.google.dagger:dagger-compiler:$daggerVersion"
kaptAndroidTest "com.google.dagger:dagger-android-processor:$daggerVersion"

Step Three — Specify an instrumented test runner

class TestApplicationRunner: AndroidJUnitRunner() {
    override fun newApplication(cl: ClassLoader?, className: String?, context: Context?) = Instrumentation.newApplication(TestApplication::class.java, context)

}

Set this as the Instrumented Test Runner in build.gradle of your app. Make sure you have:

android {
defaultConfig {
testInstrumentationRunner "com.your.app.domain.TestApplicationRunner"
}
}

Step Four — The test class

We’re mostly done. Only the classes in our modules that return mocks will be mocked. Within our test class, retrieve the TestAppplication’s component. Having used @Singleton for the dependencies will allow us to retrieve the same instances to mock certain calls.

In our test class, annotate the mocked classes and inject the test application’s singletons. We stub methods accordingly.

class ApplicationUITests {

@get:Rule
@Suppress("unused")
internal var mockitoRule = MockitoJUnit.rule()

@get:Rule
var activityTestRule = ActivityTestRule<PaywallActivity> .(PaywallActivity::class.java, true, true)
    @Inject
internal lateinit var userManager: UserManager

val user = mock(User::class.java).apply {
/* return a real user or mock the properties here */
}
    @Before
fun setupMocks() {
`when`(userManager.getUser()).thenReturn(user)
}
    @Test
fun enterActivity_showsInfo() {
onView(withText("Hello World")).check(matches(isDisplayed()))
}
}

Conclusion

Here are some considerations

  • If you are also using Kotlin, you can only mock interfaces. This should be fine since all APIs should have an interface layer regardless
  • If you are having issues with asynchronous code that uses a loosely defined callback flow, consider using Espresso Idling Resources. The official documentation code says to embed idling resource calls in production code. There is no need for that, just use them inside your mocked calls.
  • Use the right tool for the job; this is a very specific use case so thoroughly consider whether methods need to be mocked in a UI test.

In general, use UI tests to ensure that core flows maintain their functionality. They are an integration and regression test. Adding stub methods when testing this flow is a consideration that shouldn’t be taken lightly.

About the author

Augustine Kwong is a Combined Major in Computer Science and Statistics at the University of British Columbia. He worked on the Core Mobile team, working predominantly on the Android app but branched out to work on backend in Scala.