Unit Testing Test Doubles and Exploring the Mocking Framework in Kotlin
An in-depth guide to this helpful program
If you don’t know anything about unit testing, you can look at my previous articles: The Basics of Android Unit Testing and Android Unit Testing — Choosing Naming Convention and Test Cases.
What are Test Doubles
Test Doubles are used to substitute external dependencies for the initialization of the system under test.
Note: system under test (sut
) is the class or unit we are testing.
In a unit test, when we test a particular unit (class), we don’t use real implementation for its external dependencies; instead, we use Test Doubles because the real implementation is slow and unreliable.
Also, we need to test only one unit at a time (test each unit in isolation), so if any error is raised, we ensure that error is in the current unit.
Test Double Types
Fake
An object that has a lightweight implementation created specifically for test purposes. It’s not suitable for production code. For example, CacheData.
Here’s a real implementation:
A fake implementation:
Stub
Generates predefined outputs.
Mock
Verifies whether a specific function is called or not.
If yes, how many times does it get called?
What parameters are passed when it is called?
Test Doubles Example
Without external dependencies, we depended on the input by dividing it into groups and boundaries, and based on that, we were expecting the result.
With external dependencies, we depend on the result of these dependencies, so our goal becomes to test how FetchProductUseCase
(sut
) interacts with external dependencies.
Hence, we need to simulate these dependencies to cover all possible cases and see how sut
interacts with these dependencies according to these cases.
We test FetchProductUseCase
(sut
) in isolation from other external dependencies, so we need to simulate these dependencies to fit each test case by substituting them with test doubles.
So, we need to create a Test Double
class for each external dependency needed by sut
. In this case, there will be three Test Double
classes: ProductService
, ProductCache
, and ProductAnalyticManager
.
Let’s see an example to make it clearer.
First, the setup
function will be as follows:
The return type for the getProduct
function in ProductService
class is ServiceResult
as follows:
Test Case 1
We need to make getProduct
in productService
Test Double return Success
to ensure the Success
status that FetchProductUseCase
calls logFailure
or not. In this case, it should not be called at all.
Therefore, our test case will contain the following:
- Preparing Test Doubles: preparing
ProductService
’s Test Double to returnSuccess
- Validate Result: ensure that
logFailure
inProductAnalyticManager
’s TestDouble has not been called at all bysut
(FetchProductUseCase
).
Note: the above two points will be included in the next test cases without mentioning them each time.
Preparing test doubles
To do that, we need two Test Double
classes.
The first one is to tell ProductService
Test Double to return Success
so we can ensure the behavior is as expected for the Success
case.
Since this Test Double returns data, this is a Stub.
And the other one is to ensure that logFailure
in the AnalyticManager
test double is called or not by sut
(FetchProductUseCase
). In this case, logFailure
should not be called.
If logFailure
is called then the failureCounter
will increase so we can know it’s called or not.
Therefore this is Mock, as we’ve verified whether a logFailure
function is called or not.
Validate Result
Our test function, including validating the result, will be as follows:
Test Case 2
We need to make getProduct
in productService
return GeneralError
. This will ensure the GeneralError
status that FetchProductUseCase
calls logFailure
or not. In this case, logFailure
should be called once.
Preparing test doubles
To do that, we need to make some changes to ProductService
’s Test Double.
Make ProductService
test double return GeneralError
so we can ensure that the behavior is as expected in the GeneralError
case. Here’s the code:
No need to change the ProductAnalyticManager
Test Double since we already prepared it to ensure that logFailure
in ProductAnalyticManager
’s Test Double is called or not by sut
(FetchProductUseCase
). In this case, logFailure
should be called once.
Validate result
So our test function will be as follows:
Test Case 3
We need to make sure getProduct
in productService
returns ServerError
so we can ensure in the ServerError
status that FetchProductUseCase
calls logFailure
or not. In this case, logFailure
should be called once.
Preparing test doubles
To do that, we need to make some changes to ProductService
’s test double.
Make ProductService
test double returns ServerError
so it will be as follows:
Also, here, there’s no need to change ProductAnalyticManager
’s Test Double.
Validate the result
So, our test function will be as follows:
In the above test cases, we looked at mock and stub examples. For the fake ones, we can take ProductCache
’s Test Double as an example. Here’s the code:
To not make this too long, you can find the code and how I used it in the test cases via GitHub from here. You can also see the FetchProductUseCaseTest
file.
Mocking Framework
Mocking Frameworks are used to facilitate Test Doubles use by replacing them with objects like stubs and mocks.
There are multiple mocking frameworks, for example, Mockito, Mockk, etc. if you understand the above fundamentals, you will find all of the frameworks easy.
We will use Mockito as an example since it’s the most popular.
First, we need to add the @RunWith(MockitoJUnitRunner::class)
annotation for the test class to use the Mockito annotations.
Any Test Double class used in the above examples, we can annotate it with the @Mock
annotation on the field level, then the class is an empty test double.
Note: @Mock
annotation in the Mockito framework does not mean mock test double. It could be a mock or stub.
Let’s take the same example above and replace the Test Doubles with Mockito’s structure.
Test Case 1
Instead of creating a Test Double
class to return Success
, we can achieve that with the following code:
If you are new to the Mocking Frameworks, you may find the syntax weird, but you will get used to it quickly.
As the above syntax said, when the getProduct
function is called, then please return whatever we need for the test case. In this case, we need to return success
.
Therefore, the above code is a Stub.
So far, our code will be as follows:
Also, instead of using a counter in the ProductAnalyticManager
Test Double, we can use verify methods from Mockito. We can ensure that no interactions on a specific object using the verifyNoMoreInteractions
function.
So, our code will be as follows:
As you can see, preparing and verifying are simple with Mockito.
Test cases 2 and 3
This test is similar to Test case 1, but the difference is that we need to ensure logFailure
is called once.
Verifying methods like verify
and verifyNoInteractions
lets us know if the function has been called and how many times it has been called. Therefore this verification is Mock(Mock Test Double)
.
Since Fake Test Double is our Fake implementation, you can either keep it as is even if you use the Mocking framework or deal with it as you deal with the mock test double. You can find a full example here.
We don’t only have the functions mentioned above. Mockito(or other Frameworks) provide us with many functions and options that make life easier for mocking.
Some developers use frameworks, some don’t use them at all, and others use a mix. This preference depends on each developer.
Thanks for reading!
Stay tuned for more.