Troubleshooting Mockito-Kotlin in Android Testing (╯°□°)╯︵ ┻━┻
Tips, Tricks, and Gotchas with mocking in Android Unit Testing
Note: This doesn’t necessarily cover Mockito-Kotlin 2 — there are lots of changes and improvements done there that may or may not apply to nhaarman’s Mockito-Kotlin library
Unit testing is meant to test the “business logic” of your application — when these tests are run, they are executed on the development machine (host).
Writing tests in Android can seem really daunting, but it is so important for your code, especially as it grows in size and complexity! Writing solid unit tests for your classes gives you the following benefits:
- Make changes to your code with confidence
- Treat your tests as living documentation
- Encourages clean, maintainable code
Some folks prefer to write these tests strictly following Test-Driven Development (TDD), where these tests are written prior to writing the code, and that your tests fail initially. Personally, it doesn’t matter for me whether the tests are written before or after you’ve written your code, as long your tests challenges the robustness of what you wrote.
It takes a while to get the hang of writing thoughtful tests, but once you do, it can be kind of fun! This blurb is dedicated to the embarrassing amount of times I‘ve run into “gotchas”.
Resources
- Kotlin Mockito — https://github.com/nhaarman/mockito-kotlin
- Android Testing Documentation — https://developer.android.com/studio/test
Scenario 1: Invalid use of matchers? (ノ`Д´)ノ
Mockito is pretty descriptive about this mistake. Be sure you’re not combining raw values with objects in your argument matchers.
You can have all raw data, but if you have even one matcher, then the rest needs to be matchers — so wrap raw data with eq
!
The Fix:
Scenario 2: Wanted but not invoked?╰༼=ಠਊಠ=༽╯
If I’m forced to use networking or database interactions in unit testing, I’ll need to mock those calls so the JVM testing doesn’t crash. But it’s not immediately obvious.
For example, you might have a test :
That gets the following message:
Wanted but not invoked:
...
-> at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
Actually, there were zero interactions with this mock.
That doesn’t seem terribly helpful… Let’s look into the function onNewCalendarInstance()
to see if there’s anything telling.
The concept behind Data Access Objects provides a layer of abstraction over the database. When we mock, we don’t have to populate and hit the actual database implementation every time we want to test.
We must mock the DAO and then stub any call made in the function under the test. To mock member classes in a test, you can mark it with @Mock
. You don’t need to initialize it because mocking will take care of it at runtime.
Scenario 3: I’m definitely mocking the necessary calls. Why am I getting an NPE?╰༼=ಠਊಠ=༽╯
Let’s re-examine a stubbed user sign in call again:
The matchers are fine, so why might we be getting an NPE back? Are the parameters in the userSignIn
call actually expecting an object property in the class that you’re working with?
fun signInUser(user: User, setOfIds: List<Int>, dateTime: String) {
repository.userSignIn(user.id, setOfIds, dateTime)
}
Then you need to mock that as well.
Scenario 4: Flappy Tests ୧༼ಠ益ಠ༽୨
Tests are run in parallel — it’s possible to have flappy tests if your set up in one test is not explicitly defined. It may end up using the settings that shared and set in another test.
Is what you’re setting up in your before function really needed, or is it better to manually set it up yourself?
Shady:
There are only two tests here, but it is possible for the offline test to run first, which could allow the first to fail since we do not explicitly set internet connection in that particular test. This can easily be fixed by setting it explicitly in each test.
Less Shady:
You should care about the setup of your tests as well as the outcome. When you’re happy with your testing, be sure to run and check it with code coverage!