Edward Harker

Test Fixtures

When we write unit tests we often need to provide data to the system under test. For example, a test for our (Android) view model might look like this:

@Test
fun `shows user name` {
  val user = User(
    id = "1234"
    name = "ted"
    favouriteColour = "blue"
  )
  whenever(userRepository.getUserById("1234")).thenReturn(user)
 
  val actual = viewModel.name
 
 assertThat(actual).isEqualTo("ted")
}

This becomes tedious when we have more tests that need to use our User class, because we have to write the boilerplate code to create it each time. We can lift the user out to a class variable, but this doesn’t entirely solve the problem if we need a User in multiple test classes. Furthermore, our test only cares about the id and name fields, but we still have to define the favouriteColour. Every time we add a new field to User, we have to consider all of our tests and what value to set the new field to.

The solution is to create test fixtures:

object UserFixtures {
  fun user() = User(
    id = "1234"
    name = "ted"
    favouriteColour = "blue"
  )
}

Now we no longer need to add boilerplate to each test, we can simply call UserFixtures.user() .

@Test
fun `shows user name` {
  val user = UserFixtures.user()
  whenever(userRepository.getUserById("1234")).thenReturn(user)
 
  val actual = viewModel.name
 
  assertThat(actual).isEqualTo("ted")
}

This works really well to reduce boilerplate and make tests more concise, but does have issues as the codebase scales.

First of all, from just reading the test, it’s hard to see where the expected value “ted” comes from. It also creates a tight coupling between your tests because any changes to values in fixtures will likely break the tests that use them.

To avoid this problem, fixtures should be defined with default values. We can then make use of kotlin’s copy function to define the values that we care about in each test.

object UserFixtures {
  fun user() = User(
    id = ""
    name = ""
    favouriteColour = ""
  )
}
 
@Test
fun `shows user name` {
  val user = UserFixtures.user().copy(id = "1234", name = "ted")
  whenever(userRepository.getUserById("1234")).thenReturn(user)
 
  val actual = viewModel.name
 
  assertThat(actual).isEqualTo("ted")
}

We are copying the fixture to define only the values that we care about for our test. This makes it easy to see exactly which fields are important for this test and doesn’t add any coupling to the values in the fixture