Unit Testing Test Doubles and Exploring the Mocking Framework in Kotlin

An in-depth guide to this helpful program

Haitham Ayyash
Better Programming

--

Photo by Mari Helin on Unsplash

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:

cachedata (insertdata(), deletedata(), getdata()) goes to DB

A fake implementation:

cachedata (insertdata(), deletedata(), getdata()) goes to memory hashmap

Stub

Generates predefined outputs.

https://cdn-images-1.medium.com/max/1600/1*r6rgJEASK6CFAmN869ufPg.png

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 return Success
  • Validate Result: ensure that logFailure in ProductAnalyticManager’s TestDouble has not been called at all by sut (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.

--

--