Android Nomad #41 - Mocks vs Fakes

Lets explore differences between mocks and fakes for unit testing

Android Nomad #41 - Mocks vs Fakes

When it comes to unit testing in Android development, two popular approaches often come up: Mocks and Fakes. Both serve the purpose of isolating the code under test, but they have distinct characteristics and use cases.

The golden words on writing tests.

The tests are as good as the person who is writing the test cases

Hence, it is important to make an informed decision before you do go down that path.

What are Mocks?

Mocks are objects that simulate the behavior of real objects in controlled ways. They are typically used to verify that certain methods are called with specific arguments.

Example of a Mock

Let's say we have a UserRepository interface:

interface UserRepository {
    suspend fun getUser(id: String): User
    suspend fun saveUser(user: User)
}

We can create a mock of this repository using a mocking library like Mockito:

@Test
fun testUserViewModel() {
    val mockRepository = mock(UserRepository::class.java)
    val viewModel = UserViewModel(mockRepository)

    // Set up the mock behavior
    `when`(mockRepository.getUser("123")).thenReturn(User("123", "John Doe"))

    // Test the ViewModel
    viewModel.loadUser("123")

    // Verify that the repository's getUser method was called with the correct argument
    verify(mockRepository).getUser("123")
}

When to Use Mocks

  1. Verifying interactions: Mocks are excellent when you need to ensure that certain methods are called with specific arguments.
  2. Complex dependencies: When dealing with complex external dependencies that are difficult to set up in a test environment.
  3. Testing error scenarios: Mocks make it easy to simulate error conditions and test how your code handles them.

What are Fakes?

Fakes are lightweight implementations of interfaces or classes used in production code. Unlike mocks, fakes have working implementations, but they're usually simplified versions of the real implementation.

Example of a Fake

Using the same UserRepository interface, we can create a fake implementation:

class FakeUserRepository : UserRepository {
    private val users = mutableMapOf<String, User>()

    override suspend fun getUser(id: String): User {
        return users[id] ?: throw NoSuchElementException("User not found")
    }

    override suspend fun saveUser(user: User) {
        users[user.id] = user
    }
}

Now, we can use this fake in our tests:

@Test
fun testUserViewModel() {
    val fakeRepository = FakeUserRepository()
    val viewModel = UserViewModel(fakeRepository)

    // Prepare the fake data
    fakeRepository.saveUser(User("123", "John Doe"))

    // Test the ViewModel
    viewModel.loadUser("123")

    // Assert the result
    assertEquals("John Doe", viewModel.userName.value)
}

When to Use Fakes

  1. Stateful testing: Fakes are great when you need to maintain state across multiple method calls.
  2. Testing complex logic: When your production code relies on complex behavior from dependencies, a fake can provide a simplified but functional implementation.
  3. Integration-like tests: Fakes allow you to write tests that are closer to integration tests while still being fast and isolated.

Comparing Mocks and Fakes

Cost of Maintenance

The real question is which one has longer legs. Simple answer, None. Both require the same amount of time and effort at least in the short term.

If the goal is to write a ViewModel with complex interactions that grows over time, go for Fakes its simple. You basically control everything the data flow and states.

If your requirements keep changing that may include some dependencies from 3rd party, look no further than Mocks.

I borrowed the following chart from this article which precisely covers this topic.

For long term (re)use, mocks have an edge but this doesn’t apply to everyone.

Conclusion

Both mocks and fakes have their place in Android unit testing. Mocks excel at verifying interactions and simulating complex dependencies, while fakes shine in stateful testing and providing simplified but functional implementations.

When choosing between mocks and fakes, consider the specific needs of your test:

  • Use mocks when you need to verify precise interactions or simulate complex external systems.
  • Opt for fakes when you need to maintain state, test complex logic, or write tests that are closer to integration tests.

Remember, the goal is to write effective, maintainable tests that give you confidence in your code. Sometimes, a combination of both approaches might be the best solution for your testing strategy.

Subscribe to Sid Pillai

Don’t miss out on the latest issues. Sign up now to get access to the library of members-only issues.
jamie@example.com
Subscribe