At OkCupid, we often use the Mockito library for creating mocks of our dependencies to be used within our JUnit tests. This allows us to easily mock return values for certain methods, or to verify a method was called on one of our dependencies, but it can provide complications as the integration between the component under test and its dependencies become more complicated.

In this post, we're going to walk through a limitation found using a mocking library, and discuss how we were able to get past that limitation by using our own fake implementation. Let's start by setting the stage for the problem.

Setting The Stage

Before we discuss the problem, let's make sure we understand the component under test. We'll be looking at a ViewModel that is responsible for making a network request and showing some results.

Below is the snippet for the ViewModel, but if you'd like to see the related classes, you can find them in this gist. Note that in this ViewModel, as soon as it's created, we request profile information inside the init method:

class ProfileViewModel(
    userId: String,
    repository: ProfileRepository,
    backgroundScheduler: Scheduler = Schedulers.io(),
    mainScheduler: Scheduler = AndroidSchedulers.mainThread()
): ViewModel() {
    private val _state = MutableLiveData<ProfileViewState>()
    val state: LiveData<ProfileViewState> = _state
    
    init {
        _state.value = ProfileViewState.loading()
        
        repository
            .fetchProfile(userId)
            .subscribeOn(backgroundScheduler)
            .observeOn(mainScheduler)
            .subscribe(
                { user ->
                    _state.value = ProfileViewState.success(user)
                },
                { error ->
                    _state.value = ProfileViewState.error(error)
                }
            )
    }
}

As soon as our ViewModel is created, we'll emit a loading state to our LiveData. Then, we'll request a profile, and post a new ProfileViewState if the call succeeds or fails.

This is everything we need for our component under test. Next we can test it.

Testing The ViewModel

We'll start with one positive case test to make sure that when we request a user, a loading state is emitted followed by a data state. Let's see what that test looks like:

class ProfileViewModelTest {

    @Test
    fun loadProfile() {
        val testUser = User(userid = "123")
        val mockRepository = Mockito.mock(ProfileRepository::class.java)

        whenever(mockRepository.fetchProfile(anyString()))
            .thenReturn(Single.just(testUser))

        val viewModel = ProfileViewModel(
            userId = "123",
            repository = mockRepository,
            backgroundScheduler = Schedulers.trampoline(),
            mainScheduler = Schedulers.trampoline()
        )

        val observedStates = viewModel.state.testObserver().observedValues
        assertThat(observedStates.size).isEqualTo(2)

        val firstState = observedStates[0]
        val secondState = observedStates[1]
        assertThat(firstState.loading).isTrue()
        assertThat(secondState.data).isEqualTo(testUser)
    }
}

If you'd like to see the implementation of .testObserver() you can find it in this gist.

The Test Fails

To our surprise, this test will fail! We are mocking a successful data request, so we should expect that our observedStates has two entries: one for the loading state, and one for the successful data state. Upon running the test, the first assertion fails. Our test says that observedStates.size is one, and the value is the data state.

In other words, our test was not able to verify that a loading state occurred.

What Happened?

Let's think about what's different in our unit test than real code. In our unit test, we're passing Scheduler.trampoline() from RxJava which helps to make the network request run as if it were sequential. In terms of this test, it's as if the network request succeeds instantly once the ViewModel is created.

Then, after our ViewModel is created, we apply a test observer on the ViewModel.state LiveData, which is already in a loaded data state. This means the loading state happened too far back in the time - we can't observe a LiveData before it's created, and so we have no way to verify a loading state ever occurred.

This complication is caused by our mocking library, which tells our mock repository to return information right away. Instead, we can create our own fake implementation of a ProfileRepository that we have full control over, and can control that emission of data to ensure our unit test captures the loading state.

Creating A Fake

To create a fake implementation, we start by creating a new class that implements our interface. Remember, we don't want our fake to return data right away, because that will just cause the same problem. Instead, since we're using RxJava, we can implement our fake in a way that uses a BehaviorSubject behind the scenes that we can control.

class FakeRepository : ProfileRepository {
    private val userSubject: BehaviorSubject<User> = BehaviorSubject.create()
    
    override fun fetchProfile(userId: String): Single<User> {
        return userSubject.hide().firstOrError()
    }
}

The implementation you use here may change if you are using coroutines, but the concept remains the same: we don't want to return from fetchProfile() with information right away. We want to ensure that our fake implementation controls precisely when that data is emitted.

Controlling Data Emissions

Since our fake implementation is using a BehaviorSubject as the underlying data source, we can create our own public method to emit to it whenever we like:

class FakeRepository : ProfileRepository {
    // ...
    
    fun emitUser(user: User) {
        this.userSubject.onNext(user)
    }
}

Updating Tests To Validate Loading State

Now that we have a system in place where we have fine grained control over when data is emitted from our repository, we can leverage this to accurately test our loading state. We'll follow this recipe:

  1. Create our fake repository and ViewModel component
  2. Since our fake does not emit data right away, we will be able to verify that we are in a loading state.
  3. We can control the fake implementation to emit data.
  4. Last, we can verify that our ViewModel is now in a loaded data state.
class ProfileViewModelTest {

    @Test
    fun loadProfile() {
        val testUser = User(userid = "123")

        // Using a fake instead of mockito
        val fakeRepository = FakeRepository()

        // Create our ViewModel
        val viewModel = ProfileViewModel(
            userId = "123",
            repository = fakeRepository,
            backgroundScheduler = Schedulers.trampoline(),
            mainScheduler = Schedulers.trampoline()
        )

        val testObserver = viewModel.state.testObserver()
        val observedStates = testObserver.observedValues

        // Verify we only have loading state
        assertThat(observedStates.size).isEqualTo(1)
        val firstState = observedStates[0]
        assertThat(firstState.loading).isTrue()

        // Emit data, verify we move to a
        // loaded data state
        fakeRepository.emitUser(testUser)
        assertThat(observedStates.size).isEqualTo(2)
        val secondState = observedStates[1]
        assertThat(secondState.data).isEqualTo(testUser)
    }
}

Recap

Mocking libraries provide a quick solution for creating dependencies to use in our Android unit tests, but come at the expense of having limitations for controlling the behavior of those dependencies. By leveraging interfaces and our own fake implementation that exists in our code base, we have full control over the dependencies and we can use that to control the emission of data for reactive flows to thoroughly unit test our components.

I hope you found this helpful! If you have other examples of using a fake versus a mock, let me know on Twitter.

Interested in working for OkCupid? We're hiring!