Isolating Units for Tests with Mockito-Kotlin
Mockito-Kotlin creates fake objects (mocks) to stand in for real dependencies in your tests. This lets you test a class in isolation, for example, testing a ViewModel without making a real network call. The footgun is testing implementation, not behavior.
WHY IT EXISTS: When unit testing a piece of code, you want to test it in isolation. If your PaymentProcessor class depends on a NetworkClient and a Database, running a test for it would require a live network and database. This is slow, unreliable, and complex. Test doubles were invented to solve this by replacing real dependencies with fakes you can control.
THE MENTAL MODEL: A mock is a stand-in actor. Imagine you're testing a Checkout class that uses a CreditCardService. Instead of using a real service and charging a real card, you create a mock CreditCardService. You program this mock with specific instructions: "When charge() is called with $10, return 'Success'". Then you run your Checkout logic and verify that it did, in fact, call charge() on the service as expected.
HOW IT WORKS: Mockito-Kotlin is a wrapper around the popular Java library Mockito, providing a more idiomatic Kotlin syntax. It uses functions like mock<Type>() to create a fake object at runtime. You then use a block like on { mock.someMethod() } doReturn "someValue" to "stub" its behavior, defining what it should do when called. Finally, after executing your code under test, you use verify(mock).someMethod() to assert that your code interacted with the dependency correctly.
WHEN TO USE IT: Use mocks for unit testing to isolate your class under test (the "SUT"). It's perfect for replacing dependencies that are slow (network APIs), have side effects (database writes), are non-deterministic (return the current time), or are simply complex to set up for a test.
WHEN NOT TO USE IT: Do not use mocks for integration tests, where the entire point is to see how real components interact. Avoid mocking simple value objects or data classes you can easily create. The biggest pitfall is over-mocking, where tests become a fragile mirror of the implementation. If you refactor the SUT's internal logic without changing its public behavior, a heavily mocked test might break, which is a bad sign.
ONE CANONICAL EXAMPLE: Imagine testing a UserManager that depends on a UserApi to fetch data. First, in the "Given" block, you create a mock API: val mockApi = mock<UserApi>. You then program its behavior: on { mockApi.fetchUserName(1) } doReturn "Alice". This tells the mock to return "Alice" when its fetchUserName method is called with the ID 1. Next, in the "When" block, you call your method under test: userManager.getFormattedUserName(1). Finally, in the "Then" block, you verify the outcome. You assert that the result is correct, and you can also use verify(mockApi).fetchUserName(1) to confirm that your UserManager did in fact call the API as expected.
Read the original → github.com
Get five bites like this every day.
Tezvyn delivers a daily feed of 60-second tech bites with quizzes to lock in what you learn.