Dealing With Production Code In Tests

Why?

You may be wondering, “why not use production code in tests?” Sometimes it might not be possible — like calling an Android framework class in unit tests — or sometimes we don’t want to. For example, if we want to test out a retrofit network call that transfers money from one account to another, we don’t want to transfer money every time the test is called. In those cases we replace the production code with alternative implementations for testing purposes and these are called ‘Test Doubles.'

There are various types of test doubles; in this article, we will take a look at 3 commonly used ones: Fakes, Mocks, and Stubs.

Examples listed in this article are written in Kotlin, but the concepts are transferable. While it is possible to use our own implementation for providing the mocking and stubbing logic, in this article we’ll be using a mocking framework called mockito-kotlin to make generating test doubles easy.

Fakes

Fakes have working implementation but have alternative code compared to the production; sometimes it might be much simpler compared to production code.

An example for this case is using a fake datasource instead of touching the actual database or network or performing disk operations. In the following example we are making a FakeContactsDao that can be used to replace the production version in tests. This implementation is simpler compared to the production version which would have to deal with database. This is especially useful in instrumented/integration tests where you might be testing the entire implementation of the datasource.

class FakeContactsDao : ContactsDao {
  private val contacts = mutableListOf<Contact>()

  override fun contacts(): List<Contact> {
    return contacts
  }

  override fun addContact(name: String, phoneNumber: String) {
    val contact = Contact(name, phoneNumber)
    contacts.add(item)
  }
 
  override fun delete(contact: Contact) {
    contacts.remove(contact)
  }
}

Mocks

Mocks are objects that expect certain calls to be made. They will throw an assertion error when they don’t receive those calls that are expected or if they receive a call that is not expected.

We use mocks when we don’t want to invoke the production code but want to check if the action is called or check how many times a certain action is performed. This is especially useful when you cannot run the production code in tests or if you want to avoid running the production code in tests but still verify that the methods are invoked.

In the following example we have a UserPaymentsRepo that takes in PaymentsApi . When we send a payment to the user, we expect to call the PaymentsApi#payUser.

interface PaymentsApi {
  fun payUser(userId: UUID)
}

class UserPaymentsRepo(private val paymentsApi: PaymentsApi) {
  fun payUser(userId: UUID) {
    paymentsApi.payUser(userId)
  }
}

We verify that method is invoked and no other method from the PaymentsApi is called.

class UserPaymentsTest {
  private val paymentsApi = mock<PaymentsApi>()
  private val userPaymentsRepo = UserPaymentsRepo(paymentsApi)

  @Test
  fun `send payment to the selected user`() {
    val userId = UUID.fromString("1866b9ad-bfa4-4293-b68c-a6a8e467ddf6")

    userPaymentsRepo.payUser(userId)

    verify(paymentsApi).payUser(userId)
    verifyNoMoreInteractions(paymentsApi)
  }
}

Stubs

Stubs are objects that lets you control a methods behaviour in a test. They are usually placed at the top of the test.

An example for this case is returning predefined data whenever a database method is called. We are doing this to avoid calling the database in tests or testing various database cases.

Let’s say we are making a social networking app and we want to get list of starred contacts for the user.

class UserViewModel(
    private val userRepo: UserRepo
) {
  fun starredContacts(userId: UUID): List<Contact> {
    return userRepo.starredContacts(userId)
  }
}

Instead of making the database call, we provide a stub that returns a predefined list of starred contacts. Then we can assert that we received the same contacts when we are calling userViewModel.starredContacts

class UserViewModelTest {
  private val userRepo = mock<UserRepo>()
  private val userViewModel = UserViewModel(userRepo)

  @Test
  fun `get list of starred contacts`() {
    val userId = UUID.fromString("939203d1-0a0d-40a8-9f61-de582342aa76")
    val starredContacts = listOf(Contact("John"), Contact("Tom"), Contact("Jacob"))

	  // providing a stub for userRepo.starredContacts
    whenever(userRepo.starredContacts(userId)) doReturn starredContacts

    assertThat(userViewModel.starredContacts(userId)).isEqualTo(starredContacts)
  }
}

When to use

Here is how I decide when to use Fakes, Stubs, Mocks.

Fakes - To provide alternate implementation of the production code that is independent of the system and can be tested quickly.

Mocks - To mock the production code and also to verify all the actions are performed/not to be performed in the test case.

Stubs - To have a predefined response for the calls made during the test case.

Reading notes

TestDouble - Martin Fowler

Mocks Aren’t Stubs - Martin Fowler

Introduction to Test Doubles and Dependency Injection - Google Codelabs

Fundamentals of Testing - Android Developers