Using Mock Objects - The Bigger Design Picture - Pragmatic Unit Testing in Java 8 with JUnit (2015)

Pragmatic Unit Testing in Java 8 with JUnit (2015)

Part 3. The Bigger Design Picture

Chapter 10. Using Mock Objects

It’s a safe bet that you find your own system hard to test. Perhaps you’re thinking that the rest of this book makes it all look too easy. “It must be nice to have a system that supports writing unit tests out of the box, but it doesn’t match my reality,” says Pat.

In this chapter you’ll learn how to employ mock objects to break dependencies on pain-inducing collaborators, gaining a tool that will help you get past an ever-present hurdle. With mocking, you’ll be able to see more of the light at the end of the unit-testing tunnel.

A Testing Challenge

We’re adding a new feature to the iloveyouboss application. As an alternative to typing in address details, users can select a point on an interactive map that represents a Profile address. The application passes the latitude and longitude coordinates for the selected point to a retrieve() method defined on the AddressRetriever class. The point method should return a populated Address object based on the coordinates.

Lucky us, the coding is done, and it’s now our job to write a test for the retrieve() method:

iloveyouboss/mock-1/src/iloveyouboss/AddressRetriever.java

import java.io.*;

import org.json.simple.*;

import org.json.simple.parser.*;

import util.*;

public class AddressRetriever {

public Address retrieve(double latitude, double longitude)

throws IOException, ParseException {

String parms = String.format("lat=%.6flon=%.6f", latitude, longitude);

*

String response = new HttpImpl().get(

*

"http://open.mapquestapi.com/nominatim/v1/reverse?format=json&"

*

+ parms);

JSONObject obj = (JSONObject)new JSONParser().parse(response);

JSONObject address = (JSONObject)obj.get("address");

String country = (String)address.get("country_code");

if (!country.equals("us"))

throw new UnsupportedOperationException(

"cannot support non-US addresses at this time");

String houseNumber = (String)address.get("house_number");

String road = (String)address.get("road");

String city = (String)address.get("city");

String state = (String)address.get("state");

String zip = (String)address.get("postcode");

return new Address(houseNumber, road, city, state, zip);

}

}

On first glance, we think it should be straightforward to write tests for the method, since it consists of only a dozen or so statements and a sole conditional. Then we notice the code that appears to make an HTTP GET request (highlighted). Hmm.

Sure enough, the HttpImpl class interacts with Apache’s HttpComponents Client to execute a REST call:

iloveyouboss/mock-1/src/util/HttpImpl.java

import java.io.*;

import org.apache.http.*;

import org.apache.http.client.methods.*;

import org.apache.http.impl.client.*;

import org.apache.http.util.*;

public class HttpImpl implements Http {

public String get(String url) throws IOException {

CloseableHttpClient client = HttpClients.createDefault();

HttpGet request = new HttpGet(url);

CloseableHttpResponse response = client.execute(request);

try {

HttpEntity entity = response.getEntity();

return EntityUtils.toString(entity);

} finally {

response.close();

}

}

}

The HttpImpl class implements the Http interface:

iloveyouboss/mock-1/src/util/Http.java

public interface Http {

String get(String url) throws IOException;

}

We know that the HttpImpl code works, having used it in a number of other successfully deployed subsystems, so we don’t have to worry about writing tests for it. Whew. But we also know that the HttpImpl class must interact with an external service over HTTP—a recipe for unit-testing trouble. Any tests we could write against the retrieve() method on AddressRetriever will end up executing a live HTTP call, which would carry at least two significant implications:

· The tests against the live call will be slow in comparison to the bulk of our other, fast tests.

· We can’t guarantee that the Nominatim HTTP API will always be available and return consistent results. It’s out of our control.

A test version of the API (perhaps sitting on a QA server) would at least give us some control over availability, but it’d still be slow in comparison. And in all likelihood, it’d create a nuisance on occasions when the API goes down.

We focus instead on our primary goal: we want to unit-test the logic in retrieve() in isolation from any other code or dependencies. Given that we trust the HttpImpl class, what remains to test is the logic that prepares the HTTP call and the logic that populates an Address given the HTTP response.

Replacing Troublesome Behavior with Stubs

Let’s focus first on verifying how we populate an Address using the JSON response from the HTTP call. To do that, we’d like to change the behavior of HttpImpl’s get() method—just for purposes of the test we want to write—so that it returns a hardcoded JSON string. An implementation that returns a hardcoded value for purposes of testing is known as a stub.

HttpImpl implements the functional Http interface. Create a stub implementation dynamically using lambdas:

iloveyouboss/mock-2/test/iloveyouboss/AddressRetrieverTest.java

Http http = (String url) ->

"{\"address\":{"

+ "\"house_number\":\"324\","

+ "\"road\":\"North Tejon Street\","

+ "\"city\":\"Colorado Springs\","

+ "\"state\":\"Colorado\","

+ "\"postcode\":\"80903\","

+ "\"country_code\":\"us\"}"

+ "}";

Or, if you’re more comfortable with anonymous inner classes:

iloveyouboss/mock-2/test/iloveyouboss/AddressRetrieverTest.java

Http http = new Http() {

@Override

public String get(String url) throws IOException {

return "{\"address\":{"

+ "\"house_number\":\"324\","

+ "\"road\":\"North Tejon Street\","

// ...

}};

How did we come up with that JSON string? We worked through the parsing code in the retrieve() method to see what it could parse.

Defining this stub gets us halfway toward being able to write our test. We still need a way to tell AddressRetriever to use our stub instead of the production implementation in HttpImpl. We decide to use a technique fancily called dependency injection, which in simple terms means that we pass the stub to an AddressRetriever instance, or inject it. For now, we choose to inject the stub via a constructor on AddressRetriever.

To support constructor dependency injection, add a constructor that takes an Http instance as a parameter and assigns it to a new field named http. In the retrieve() method, simply dereference the http field to call the get() method. Here are the changes, highlighted:

iloveyouboss/mock-2/src/iloveyouboss/AddressRetriever.java

public class AddressRetriever {

*

private Http http;

*

public AddressRetriever(Http http) {

*

this.http = http;

*

}

public Address retrieve(double latitude, double longitude)

throws IOException, ParseException {

String parms = String.format("lat=%.6flon=%.6f", latitude, longitude);

*

String response = http.get(

*

"http://open.mapquestapi.com/nominatim/v1/reverse?format=json&"

*

+ parms);

JSONObject obj = (JSONObject)new JSONParser().parse(response);

// ...

}

}

Now we can write the test:

iloveyouboss/mock-2/test/iloveyouboss/AddressRetrieverTest.java

import java.io.*;

import org.json.simple.parser.*;

import org.junit.*;

import util.*;

import static org.hamcrest.CoreMatchers.*;

import static org.junit.Assert.*;

public class AddressRetrieverTest {

@Test

public void answersAppropriateAddressForValidCoordinates()

throws IOException, ParseException {

Http http = (String url) ->

"{\"address\":{"

+ "\"house_number\":\"324\","

+ "\"road\":\"North Tejon Street\","

+ "\"city\":\"Colorado Springs\","

+ "\"state\":\"Colorado\","

+ "\"postcode\":\"80903\","

+ "\"country_code\":\"us\"}"

+ "}";

AddressRetriever retriever = new AddressRetriever(http);

Address address = retriever.retrieve(38.0,-104.0);

assertThat(address.houseNumber, equalTo("324"));

assertThat(address.road, equalTo("North Tejon Street"));

assertThat(address.city, equalTo("Colorado Springs"));

assertThat(address.state, equalTo("Colorado"));

assertThat(address.zip, equalTo("80903"));

}

}

Here’s what happens when the test runs:

· The test creates a stub instance of Http for which its sole method (get(String url)) returns a hardcoded JSON string.

· The test creates an AddressRetriever, passing the stub to its constructor.

· The AddressRetriever stores the stub.

· When executed, the retrieve() method first formats the parameters passed to it. It then calls the get() method on the http field, which stores the stub. The retrieve() method doesn’t care whether http holds a stub or the production implementation; all it knows is that it’s interacting with an object that implements the get() method.

· The stub returns the JSON string we hardcoded in the test.

· The rest of the retrieve() method parses the hardcoded JSON string and populates an Address object accordingly.

· The test verifies elements of the returned Address object.

Changing Our Design to Support Testing

Our new code represents a small change to the design of the system. Before, the Http instance was created by the retrieve() method as a private detail of the AddressRetriever class. Now, any client that interacts with AddressRetriever is responsible for creating and passing in an appropriateHttp instance, perhaps something like:

AddressRetriever retriever = new AddressRetriever(new HttpImpl());

Is changing your system’s design just to write a test a bad thing? No, because it’s most important to demonstrate, in a simple fashion, that the system behaves the way you expect. Also, you have a better design: the dependency on Http is now declared in the clearest way possible, and moving the dependency to the interface loosens the coupling a bit.

You’re not limited to constructor injection. Many other ways to inject stubs are available, including some that require no changes to the interface of your class. You can use setters instead of constructors; you can override factory methods; you can introduce abstract factories; and you can even use tools such as Google Guice or Spring that do the injection somewhat magically.

Adding Smarts to Our Stub: Verifying Parameters

Our Http stub always returns the same hardcoded JSON string, regardless of the latitude and longitude passed to its get() method. That’s a small hole in testing. If the AddressRetriever doesn’t pass the parameters correctly, we have a defect.

“How hard can it be to pass a couple arguments correctly to a function?” asks Pat. “Do we really need to test that?”

Dale says, “You’re forgetting when we shipped code the other week where someone inadvertently swapped the order of the latitude and longitude in another part of the system. We wasted a couple hours on that defect.”

Here’s another way to think about what we’re doing: we’re not exercising the real behavior of HttpImpl, but we know that other tests exist for it. We’re exercising the rest of the code in retrieve() based on a return value that HttpImpl might cough up. The only thing left to cover is to verify that the code in retrieve() correctly interacts with the HttpImpl code.

Add a guard class to the stub that verifies the URL passed to the Http method get(). If it doesn’t contain the expected parameter string, explicitly fail the test at that point:

iloveyouboss/mock-3/test/iloveyouboss/AddressRetrieverTest.java

import java.io.*;

import org.json.simple.parser.*;

import org.junit.*;

import util.*;

import static org.hamcrest.CoreMatchers.*;

import static org.junit.Assert.*;

public class AddressRetrieverTest {

@Test

public void answersAppropriateAddressForValidCoordinates()

throws IOException, ParseException {

Http http = (String url) ->

{

*

if (!url.contains("lat=38.000000&lon=-104.000000"))

*

fail("url " + url + " does not contain correct parms");

return "{\"address\":{"

+ "\"house_number\":\"324\","

+ "\"road\":\"North Tejon Street\","

+ "\"city\":\"Colorado Springs\","

+ "\"state\":\"Colorado\","

+ "\"postcode\":\"80903\","

+ "\"country_code\":\"us\"}"

+ "}";

};

AddressRetriever retriever = new AddressRetriever(http);

// ...

}

The stub has a little bit of smarts now. It’s close to being something known as a mock. A mock is a test construct that provides emulated behavior and also does the job of verifying whether or not it received all the parameters expected.

Our smart stub pays off—we find that our tests now fail. The formatted parameter string is missing an ampersand (&):

iloveyouboss/mock-3/src/iloveyouboss/AddressRetriever.java

public Address retrieve(double latitude, double longitude)

throws IOException, ParseException {

*

String parms = String.format("lat=%.6flon=%.6f", latitude, longitude);

String response = http.get(

"http://open.mapquestapi.com/nominatim/v1/reverse?format=json&"

+ parms);

JSONObject obj = (JSONObject)new JSONParser().parse(response);

// ...

}

Simplifying Testing Using a Mock Tool

We consider transforming our smart stub into a mock as the next step. To do so would involve:

· Specifying in the test which parameters we expected (as opposed to within the stub itself)

· Trapping and storing the parameters passed to the get() method

· Supporting the ability to verify upon test completion that the stored parameters to get() contain the expected parameters

Creating a mock that performs those steps seems like overkill. What does it buy us? Actually, not much at all. But if we were to write a second or third test that used the same mock, we’d shrink the amount of code we’d need to write for each.

And if we created more mock implementations for other troublesome dependencies, we’d find a way to refactor the redundancy between them. We’d end up with a general-purpose tool that would allow us to quickly bang out tests employing mocks. Our tests would be smaller and would more concisely declare what they’re trying to prove.

Rather than reinvent the wheel, we instead choose to find the fruits of someone else who’s done that work of designing a general-purpose mock tool. Mockito[36] is such a fruit (though its creators would say it’s more of a cocktail).

Setting up Mockito is a matter of downloading some JARs and configuring your project to point to them. Once it’s set up, the tests you write that use Mockito should statically import everything in org.mockito.Mockito. Here’s a complete test that uses Mockito (including the importstatement):

iloveyouboss/mock-4/test/iloveyouboss/AddressRetrieverTest.java

// ...

*

import static org.mockito.Mockito.*;

public class AddressRetrieverTest {

@Test

public void answersAppropriateAddressForValidCoordinates()

throws IOException, ParseException {

*

Http http = mock(Http.class);

*

when(http.get(contains("lat=38.000000&lon=-104.000000"))).thenReturn(

"{\"address\":{"

+ "\"house_number\":\"324\","

// ...

+ "}");

AddressRetriever retriever = new AddressRetriever(http);

Address address = retriever.retrieve(38.0,-104.0);

assertThat(address.houseNumber, equalTo("324"));

// ...

}

The first statement in the test tells Mockito to synthesize a mock instance that implements the Http interface. This mock does all the dirty tracking and verifying work behind the scenes.

The second statement in the test starts by calling the when() static method on org.mockito.Mockito to set up the expectations for the test. It completes by calling thenReturn() on the expectation—meaning that, when the expectation is met, the mock returns the specified value. You can paraphrase the code and quickly understand what the mock is set up to do: when a call to the http method get() is made with a parameter containing the string "lat=38.000000&lon=-104.000000", then return the hardcoded JSON string.

This setting of expectations for the test is done prior to executing the act part of the test.

The next statement in the test, as before, injects the Mockito mock into the AddressRetriever via its constructor.

Finally, in the act part of the test: when the retrieve() method is called, its code interacts with the Mockito mock. If the Mockito mock’s expectations are met, it returns the hardcoded JSON string. If not, the test should fail.

The when(...).thenReturn(...) pattern is one of a number of ways to set up mocks using Mockito, but it’s probably the simplest to understand and code. It distills the effort of setting up a mock into what’s essentially a one-liner that’s immediately understood by code readers.

As an alternative to when(...).thenReturn(...), you might want to verify that a certain method was called as part of processing. There’s an example of that using Mockito’s ‘verify()‘ construct later in the book.

One Last Simplification: Introducing an Injection Tool

Passing a mock to a target class using a constructor is one technique. It requires a change to the interface and exposes a private detail to another class in the production code. Not a great deal, but you can do better by using a dependency injection (DI) tool. You’ll find a handful or more of DI tools out there, including Spring DI and Google Guice.

Because we’re using Mockito, however, we’ll use its built-in DI capabilities. The DI power in Mockito isn’t as sophisticated as you might find in other tools, but most of the time you shouldn’t need anything more.

Using DI in Mockito means following these steps:

1. Create a mock instance using the @Mock annotation.

2. Declare a target instance variable annotated with @InjectMocks.

3. After instantiating the target instance, call MockitoAnnotations.initMocks(this).

Here’s the code:

iloveyouboss/mock-5/test/iloveyouboss/AddressRetrieverTest.java

public class AddressRetrieverTest {

*

@Mock private Http http;

*

@InjectMocks private AddressRetriever retriever;

*

@Before

*

public void createRetriever() {

*

retriever = new AddressRetriever();

*

MockitoAnnotations.initMocks(this);

*

}

@Test

public void answersAppropriateAddressForValidCoordinates()

throws IOException, ParseException {

when(http.get(contains("lat=38.000000&lon=-104.000000")))

.thenReturn("{\"address\":{"

+ "\"house_number\":\"324\","

// ...

}

}

And here’s a paraphrase of the preceding code:

· Declare the http field and annotate it with @Mock, indicating that it’s where you want the mock to be synthesized.

· Declare the retriever field and annotate it with @InjectMocks, indicating that it’s where you want the mock to be injected.

· In the @Before method, create an instance of AddressRetriever.

· Call MockitoAnnotations.initMocks(this). The this argument refers to the test class itself. Mockito retrieves any @Mock-annotated fields on the test class and synthesizes a mock instance for each (effectively running the same code as the earlier explicit call,org.mockito.Mockito.mock(Http.class). It then retrieves any @InjectMocks-annotated fields and injects mock objects into them (our AddressRetriever instance, in our case).

To inject mock objects, Mockito first seeks an appropriate constructor to use. If it finds none, it seeks an appropriate setter method. It finally seeks an appropriate field (it starts by trying to match on the type of the field). Cool! You want to use this feature, so eliminate the constructor onAddressRetriever:

iloveyouboss/mock-5/src/iloveyouboss/AddressRetriever.java

public class AddressRetriever {

*

private Http http = new HttpImpl();

public Address retrieve(double latitude, double longitude)

throws IOException, ParseException {

String parms = String.format("lat=%.6f&lon=%.6f", latitude, longitude);

String response = http.get(

"http://open.mapquestapi.com/nominatim/v1/reverse?format=json&"

+ parms);

JSONObject obj = (JSONObject)new JSONParser().parse(response);

// ...

Mockito magically finds our http field and injects the mock instance into it!

The beauty of field-level injection is that we no longer need to require clients to construct and pass in an implementation of Http. We instead provide a default implementation at the field level (highlighted in the preceding code).

What’s Important to Get Right When Using Mocks

In the best case, you end up with a single-line arrange portion of your test that creates an expectation using Mockito’s when(...).then(...) construct. You have a single-line act, and you have a single assert. These are tests you can quickly read, understand, and trust.

Tests using mocks should clearly state what’s going on. One way we do this is by correlation. In answersAppropriateAddressForValidCoordinates, it’s clear that the expected parameter string of "lat=38.000000&lon=-104.000000" correlates to the act arguments of 38.0 and -104.0. Things obviously aren’t always this easy, but the more you can help the test reader make that connection without having to dig through other code, the better your tests will be.

Don’t forget that mocks replace real behavior. You want to ask yourself a few questions to make sure you’re using them safely.

Does your mock really emulate the way the production code works? Does the production code return other formats you’re not thinking of? Does it throw exceptions? Does it return null? You’ll want a different test for each of these conditions.

Does your test really use the mock or are you accidentally still triggering production code? In many cases, it’s obvious; in some cases, it’s more subtle. If you were to turn off the mock and let retrieve() interact with the HttpImpl production class, you’d notice a slight slowdown on the test run (you can actually watch the JUnit progress bar pause for a split second). But others might not notice. One simple thing you can do is to temporarily throw a runtime exception from the production code. If you see an exception thrown when you run the test, you know you’re hitting the production code. Don’t forget to delete the throw statement when you’re done fixing the test!

A perhaps better route is to use test data that you know is not what the production call would return. In our test, we passed neat whole numbers for latitude and longitude, and we know they don’t correspond to the expected address in Colorado Springs. If we were using the real HttpImpl class, our test expectations would fail.

Finally, remember that you’re not testing the production code directly. Any time you introduce a mock, recognize that you are creating gaps in test coverage. Make sure you have an appropriate higher-level test (perhaps an integration test) that demonstrates end-to-end use of the real class.

images/aside-icons/tip.png

A mock creates a hole in unit-testing coverage. Write integration tests to cover these gaps.

After

In this chapter you learned the important technique of introducing stubs and mocks to emulate behavior of dependent objects. Your tests don’t have to interact with live services, files, databases, and other troublesome dependencies! You also learned how to use a tool to simplify your effort in creating and injecting mocks.

You’ve focused on making sure the production code is clean and well-designed in this and the prior two chapters. Doing so will extend the life of your system. However, the bigger design picture isn’t complete without also continually refactoring your tests. You’ll focus on some test cleanup in the next chapter.

Footnotes

[36]

https://code.google.com/p/mockito/