Dealing with production code in tests

3 minute read
Production Code header

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.

Screenshot 2021 01 07 at 12 39 12

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.

Screenshot 2021 01 07 at 12 39 45
Screenshot 2021 01 07 at 12 40 14

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.

Screenshot 2021 01 07 at 12 40 44

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
Screenshot 2021 01 07 at 12 41 36

When to use what

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.

If you'd like to read more, these are some great resources to bookmark:

TestDouble: Martin Fowler

Mocks Aren’t Stubs: Martin Fowler

Introduction to Test Doubles and Dependency Injection: Google Codelabs

Fundamentals of Testing: Android Developers

We’re hiring!

If the Obvious Way makes sense to you, let us buy you some (virtual) breakfast. We’ll tell you more about the work that we do, give you the lowdown on life at Obvious, and answer any questions that you might have about engineering or working here. Or jump straight in, and apply to join our team!