Mocking Out Dependencies
We'll cover the following
Dependencies often make testing hard. Our code needs to get the status of airports from a remote web service. The response from the service will be different at different times due to the nature of the data. The web service may fail intermittently, the program may run into a network failure, and so on. All these situations make it hard to write unit tests, which should be FAIR, for a piece of code that has to talk to such non-deterministic, and by their nature, unreliable external dependencies. This is where mocks come in.
A mock is an object that stands in for the real object, much like how a stunt person, instead of your favorite high-paid actor, stands in or jumps off a cliff in an action thriller. During normal execution the code under test will use the real dependency. During automated testing, however, the mock will replace the real dependency so the test can be run FAIRly.
Using mock
To facilitate unit testing of code with dependencies, the test will prepare a mock to return a canned response, attach the mock to the code under test, run the test, verify the result is as expected, and, finally, verify with the mock that the code under test interacted with its dependency as expected.
To learn how to create and use mocks, let’s turn our attention to the method that will get data from the remote FAA web service. Given an airport code, the web service returns a JSON response with the airport status data. Getting this data involves two actions: first, we have to send a request to the service URL and, second, we have to parse the response and deal with any possible errors.
Parsing data is straightforward; given a string containing JSON data, extract the necessary details from it. If there was an error, deal with it appropriately. We can easily write tests for that. It’s getting the data from the URL that makes this feature unpredictable and hard to test. We can mock that part out by using a level of indirection.
We can devise the solution using two methods instead of one. A getAirportData() function can rely upon a fetchData()
function. We’ll write the fetchData()
function, to get the real data from the FAA web service, later. For now, we’ll leave it with a TODO implementation, by throwing an exception if it’s called. The getAirportData() function can then call fetchData()
and parse the response JSON string to extract the data. In the test, we’ll mock out the fetchData()
function; when getAirportData()
calls fetchData()
, the call will go to the mock function instead of calling the real implementation. We’ll program the mock function to return a desired canned response.
Many mocking tools are available on the JVM. For example, Mockito is one of the more popular mocking tools for Java. You can use that for mocking dependencies in Kotlin code as well. However, in this chapter we’ll use Mockk for a few good reasons. First, Mockk is capable of easily mocking final
classes, and that goes in hand with the fact that in Kotlin classes are final
by default.
Second, Mockk offers nice capabilities to mock dependencies on singleton objects/companion objects and also to mock extension functions. Mockk also provides facilities to test coroutines. In short, when we use features that are specific to Kotlin, we can benefit from a tool that was created to deal with those.
Creating an interaction test
Let’s start with a test for the getAirportData()
method of Airport
, where we’ll mock fetchData()
to return a canned JSON response. In the test, we’ll verify that getAirportData()
called the fetchData()
function. This is an interaction test as opposed to an empirical test.
Before we can dive into the test, we have to prepare to mock the fetchData()
function. For this, we first need to import the functions from the Mockk library. While at it, let’s also bring along a few more imports we’ll need to create the pre- and post-listeners to run code before and after each test.
In the top of the AirportTest.kt
file, after the current imports, add the following import statements:
// AirportTest.kt
import io.kotlintest.TestCase
import io.kotlintest.TestResult
import io.mockk.*
We’ll design the fetchData()
function to be part of the Airport’s
companion object. To mock that function, we’ll have to create a mock of the Airport
singleton companion object. We can achieve this in a special beforeTest()
function. The pair of functions beforeTest()
and afterTest()
sandwich each test, so that the code within beforeTest()
runs before each test and the code within afterTest()
runs after each test. In the AirportTest
class, right after the fields and before the init()
function, add the following two functions:
Get hands-on with 1200+ tech skills courses.