In a way, the wrong turn helped me. Injecting a mock could have been an effective testing technique, but it would also couple the test to the implementation more tightly through the use of an internal type. This is the biggest danger in testing with mocks: the degree of coupling they introduce. [7f74b88] introduces a nicely uncoupled test, shown in Listing 14-4.
@Test
public void testRetrieveResponse_DomainOnly()
throws IOException {
WebRetriever sut = new WebRetriever();
HttpResponse response =
sut.retrieveResponse("www.example.com");
assertThat(response, is(notNullValue()));
}
The concept behind the mock approach would have allowed me to inspect the URL passed into the library executing the retrieval. As currently formulated, I pass the URL as a string, which leads to all the challenges of string verification discussed in Chapter 7 except that I do not control the code; it happens inside of HttpClient
. This led me to consider two points.
1. I would prefer to have a more structured way to inspect the format of the URL.
2. I probably need to create my own seam because HttpClient
does not support what I need to use its components as test doubles.
The first point led me quickly to consider the java.net.URI
class for a more structured understanding of the URL. It provides the ability to parse URI strings, and HttpClient
accepts it as an alternate format for URL arguments.
The URI
class also suggested how I could create the seam. The URI
class provides more structure and HttpClient
accepts it, but the URLs still come in from the command-line arguments as strings. This suggests the need for a translation layer, an ideal place for a seam! Changing retrieveResponse()
to convert the string URL to a URI
to give to the HttpGet
constructor [228d5c7] lets us refactor an overload of retrieveResponse()
that takes a URI
argument (Listing 14-5).
protected HttpResponse retrieveResponse(String URI)
throws IOException, URISyntaxException {
URI uri = new URI(URI);
return retrieveResponse(uri);
}
protected HttpResponse retrieveResponse(URI uri)
throws IOException {
HttpGet httpGet = new HttpGet(uri);
return httpClient.execute(httpGet);
}
Commit [05e5c03] creates a spy to capture the ultimate URI
that is used to construct an HttpGet
and uses it to verify that it handles the scheme according to spec. The resulting test and spy are shown in Listing 14-6.
@Test
public void testRetrieveResponse_DomainOnly()
throws IOException, URISyntaxException {
WebRetrieverURISpy sut = new WebRetrieverURISpy();
sut.retrieveResponse("www.example.com");
URI actualURI = sut.getSuppliedURI();
assertThat(actualURI.getHost(),
is(equalTo("www.example.com")));
assertThat(actualURI.getScheme(), is(equalTo("http")));
}
private class WebRetrieverURISpy extends WebRetriever {
URI suppliedURI;
@Override
protected HttpResponse retrieveResponse(URI uri)
throws IOException {
this.suppliedURI = uri;
return createMockResponse("");
}
public URI getSuppliedURI() {
return suppliedURI;
}
}
The spy inserts itself in the middle and captures the URI
for later inspection, invoking the behavior of the overridden method so that the actual behavior proceeds unhindered. This allows the test to obtain the structured representation of the URI for the assertions. The test captures the deficiency in the code. I fixed it in the same commit. The next few commits (up to [897bfab]) do some refactoring, add a test to assure that the existing supported behavior still works, and enforce that the only supported scheme is HTTP.
52.15.74.25