Spy Craft

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.

Listing 14-4: An implementation-independent test for the behavior of defaulting the protocol to HTTP

@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).

Listing 14-5: The refactored retrieveResponse() gives us a seam to exploit.

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.

Listing 14-6: Modifying the test from Listing 14-4 to create a spy exploiting the seam introduced in Listing 14-5

@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.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.144.227.9