Blog Infos
Author
Published
Topics
Published
Topics
Mocks, stubs, fakes, dummies, and spies on Android: from theory to (good) practice

In the first part of this series, we learned the theory involving the usage of test doubles. Now let’s cover how to use them in the Android world with some examples!

How to use test doubles on Android

In case we would like to create test doubles manually, there’s not much of a secret on Android. You just need to implement them correctly following the real dependency contract (like in most platforms or languages), and replace the real collaborator with the fake one. Dependency injection techniques can help us with this task.

Distinctions from other technologies start to emerge when we talk about tools. In the Android world, we can use frameworks that are compatible with Java and Kotlin languages to create our doubles. Among the most famous we can mention Mockito (for Java) and MockK (for Kotlin).

Below some examples using MockK:

@Test
fun `Retrieve notes count from server when requested`() {
val notesApiStub = mockk<NotesApi>() //Stub Double
val noteRepository = NoteRepository(notesApiStub)
val note = generateDummyNote() //Dummy Double
every { notesApiStub.fetchAllNotes() } returns listOf(note, note)
val allNotesCount = noteRepository.getNoteCount()
assertEquals(expected = 2, actual = allNotesCount)
}
view raw StubMockk_En.kt hosted with ❤ by GitHub

Configuring a Stub using Mockk

@Test
fun `Track analytics event when creating new note`() {
val analyticsWrapperMock = mockk<AnalyticsWrapper>() //Mock Double
val noteAnalytics = NoteAnalytics(analyticsWrapperMock)
noteAnalytics.trackNewNoteEvent(NoteType.Supermarket)
//Observes that specific operation has happened
verify(exactly = 1) { analyticsWrapperMock.logEvent("NewNote", "SuperMarket") }
}
view raw MockMockk_En.kt hosted with ❤ by GitHub

Configuring a Mock using Mockk

Doubles and maintenance costs

As mentioned in the previous article all test doubles can be configured manually or by external tools. Bear in mind that when we use external tools such as MockK or Mockito for this purpose, although they are easier to configure initially, they tend to couple our test code with implementation details from the dependencies.

Explaining with more details. The theory says that if we are testing a behavior, any modification that doesn’t change this behavior (let’s say a refactor) shouldn’t break the test code.

@Test
fun `Retrieve notes count from server when requested`() {
val notesApiStub = mockk<NotesApi>() //Stub Double
val noteRepository = NoteRepository(notesApiStub)
val note = generateDummyNote() //Dummy Double
every { notesApiStub.fetchAllNotes() } returns listOf(note, note)
val allNotesCount = noteRepository.getNoteCount()
assertEquals(expected = 2, actual = allNotesCount)
}
view raw StubMockk_En.kt hosted with ❤ by GitHub

Configuring a Stub using Mockk

 

In the example above, doing a refactor in the NoteApi code, such as changing the fetchAllNotes() method to receive a new parameter, shouldn’t break the NoteRepository test… but unfortunately in the example above it will break, as the test shares NoteApi implementation details as part of the MockK configuration (Line 6). If you imagine multiple tests doing the same configuration in multiple parts of the code, we could potentially have multiple broken tests due to a class refactor, generating overhead and maintenance costs on every refactor.

On the other side of the coin we can find the manually-configured doubles, or doubles that use the same contract as the real class for their setup. They have the drawback of potentially increasing the code base size in the beginning, but also have the benefit of not leaking implementation details to the tests as they don’t depend on any tooling magic. This can potentially improve the maintainability in the long run.

Below a comparison on how different types of double configuration can impact your code base maintainability:

Non-scientific comparison of test maintanance costs as the codebase size increases

Based on the above, for big code bases, my recommendation is to avoid configuring your doubles with tools that make you leak implementation details. This is counter-intuitive as you may think you are reinventing the wheel by not using those tools, however every new framework/library added to you project will imply in a trade-off, so it’s always good to know what the impact is and how to use the tool appropriately.

One important thing to mention is that I am not advocating against the tools, but against their misuse, as mentioned in the tweet below:

Post by Rafa Araujo describing poor usage of MockK by the Android community

Depending on the scenario, you might consider using the sociable testing school to reduce the test double boilerplate code as well. As described in the tweet below:

Jake Wharton’s post mentioning the sociable testing school and recommending the use of Fakes.

In the following section, we are going to see some test doubles strategies that could be used in your Android code base, those strategies can be applied independently of the double configuration you choose (manual or via tooling).

Utilization of test doubles in non-instrumented tests

At the test pyramid’s base we find the non-instrumented tests, which are tests that don’t use emulators or real devices during their execution. The tests in this group are known to have a high degree of isolation and speed, therefore the usage of doubles will be very frequent here.

To demonstrate the usage of test doubles in the Android world, let’s see what an application would look like in a MVVM architecture:

Example of MVVM architecture. In green we can find the components that usually depend on the Android platform (View and Local Data Source) to execute their tests. In blue the components that usually do not depend on the Android platform (ViewModel, Repository and Remote Data Source) to execute their tests.

In my experience, I’ve seen the school of solitary testing to be the most common in Android code bases and in most scenarios, you are going to work on a project that already uses (or misuses) test doubles. So I’ll try to demonstrate a testing strategy that makes use of the school of solitary testing and how can you possibly improve your current setup:

Example of how to apply test doubles using MVVM in the non-instrumented layer.

Job Offers

Job Offers

There are currently no vacancies.

OUR VIDEO RECOMMENDATION

,

Put Your Tests on a Diet:Testing the Behavior and Not the Implementation

How do you write tests? How much time do you spend writing tests? And how much time do you spend fixing them when refactoring?
Watch Video

Put Your Tests on a Diet:Testing the Behavior and Not the Implementation

Stelios Frantzeskakis
Staff Engineer
PSS

Put Your Tests on a Diet:Testing the Behavior and Not the Implementation

Stelios Frantzeska ...
Staff Engineer
PSS

Put Your Tests on a Diet:Testing the Behavior and Not the Implementation

Stelios Frantzes ...
Staff Engineer
PSS

Jobs

In this strategy above, we use the following reasoning:

Testing the View

View layer tests are not very common in the non-instrumented part. For us to create them we will probably need to build a Robolectric test, which is known to be slower than pure Java/Kotlin tests.

In case you test your Views, as we are dealing with an expensive test, my recommendation is that we don’t use doubles for the ViewModel, but for the Repository. The reason is because ViewModels (by design) are components tightly coupled with Views and therefore the behavior of the View would be better represented if we used real ViewModels. Also, we wouldn’t have much gain in speed if we used a double for the ViewModel, this test would be slow anyway. In this specific example, the Repository would be well replaced by a Stub.

Testing the ViewModel

To test the ViewModel layer, we can do an Android-free unit test and replace the Repository with a Stub, straight to the point. Other ViewModel’s dependencies could also be replaced by test doubles.

Testing the Repository

To test the Repository layer, we can also do an Android-free unit test. Here, we replace the Local Data Source and the Remote Data Source with Stubs or Fakes.

Testing the Remote Data Source

To test the Remote Data Source layer, we will probably have to replace the backend with some double. The easiest way would be to create a Fake using the MockWebServer tool, which will simulate an HTTP server responsible for returning pre-configured responses. Tests in this layer are useful to assert that the serialization of the backend responses is working correctly.

Testing the Local Data Source

To test the Local Data Source layer, we will probably have to replace the local database with a Fake or Stub. If we want to do this kind of testing on the non-instrumented layer, Robolectric could be an option as well. Even so, I believe that this type of testing might be more useful in the instrumented part of your test suite. To test the data layer in isolation, prefer to do it more reliably.

Keep in mind that this is just an example, if you have a well structured test double strategy and you use them well in your code base, you will have less trouble to switch to other test school or replace tools if you need to. Lastly, remember that the best approach will always depend on your team’s context.

Use of doubles in instrumented tests

The test doubles are also very important for the instrumented area of ​​the pyramid, but they should be used a little bit more carefully. Loading Android resources and running tests using an emulator or a real device is certainly a costly operation, perhaps more expensive than the vast majority of non-instrumented tests.

Since we will have a higher cost to build this type of infrastructure, we should make the most of it and integrate as many real components as possible, and consequently, use less test double. One common mistake I see are UI tests replacing the ViewModels by doubles. In my opinion, the more expensive is the test, the closer you should get to reality, which means that more code you should try cover. By using doubles to replace ViewModels in UI tests will simply lead to a complex and slow test with low code coverage.

Also, another point of attention is that most tools (like MockK or Mockito) manipulate bytecode to create Mocks and Stubs. As Android uses its own virtual machine and generates bytecode with a specific format (Dalvik), some of these tools have limitations to create doubles in this instrumented layer. For those reasons, I advise against using stunt doubles for instrumented tests. With the exception of two situations:

  • To replace dependencies on I/O boundaries, such as backends and databases, with faster, more deterministic doubles. Making use of tools like MockWebServer for example.
  • Or to replace third-party tools that are difficult to configure in instrumented tests, such as Firebase classes.

 

Test double strategy in instrumented tests

 

Dependency injection tools such as DaggerHilt or Koin, and Product Flavours can be great allies when using test doubles in the instrumented layer.

Android community and test doubles

As we saw in the previous sections, we have doubles that have similar purposes but are implemented and configured in different ways. Discussions about how you should use doubles and the trade-offs around configuration simplicity and maintenance have brought many interesting debates and publications in the development community.

The relationship between test fragility and refactoring is one of the most common topics you will see. Just some examples 👇

Ryan Harter’s post promoting his (great) article on replacing Mocks with Fakes. Perhaps the term Spy (instead of Fake) would be more appropriate in his article.
Post by Zac Sweers apparently unsatisfied with the way in which test doubles are generated by Mockito, which in his view, contributes to the fragility of tests.
Post by Jake Wharton advocating agains the use of mocks, probably following a sociable testing approach
Conclusion

You should now be able to answer, or at least understand, the questions asked in the introduction of the first article:

  • “We need to mock this dependency and everything will work fine” 🙌
  • “Avoid using Mocks!” 😱
  • “Mocks vs Stub?” ⚔️
  • “Prefer using Fakes than Mocks” 🤔

Test doubles (also generically referred as Mocks) are very important to your testing strategy. Get to know their concept well, understand their trade-offs and with that, keep testing your Android application in a scalable and easy way.

If you learned something from this article, leave your claps 👏 So I can know that it was helpful to you. Any feedback or questions, send them on Twitter or in the comments of this post. Thanks!

 

 

This article was originally published on proandroiddev.com on May 23, 2022

YOU MAY BE INTERESTED IN

YOU MAY BE INTERESTED IN

blog
Automation is a key point of Software Testing once it make possible to reproduce…
READ MORE
blog
Every good Android application should be well tested to minimize the risk of error…
READ MORE
blog
In this article we’ll go through how to own a legacy code that is…
READ MORE
blog

Running Instrumented Tests in a Gradle task

During the latest Google I/O, a lot of great new technologies were shown. The…
READ MORE

Leave a Reply

Your email address will not be published. Required fields are marked *

Fill out this field
Fill out this field
Please enter a valid email address.

Menu