XNSIO
  About   Slides   Home  

 
Managed Chaos
Naresh Jain's Random Thoughts on Software Development and Adventure Sports
     
`
 
RSS Feed
Recent Thoughts
Tags
Recent Comments

Fluent Interfaces improve readability of my Tests

Recently I was working on a Domain Forwarding Server. For Ex. if you request for http://google.com and Google wants to redirect you to http://google.in, then this server handles the redirection for you. There are many permutations and combination of how you want to forward your domain to another domain.

First pass, I wrote a tests:

1
2
3
4
5
6
7
8
@Test
public void permanentlyRedirectDomainsWithPath()  {
    when(request.hostName()).thenReturn("google.com");
    when(request.protocol()).thenReturn("HTTP/1.1");
    when(request.path()).thenReturn("/index.html");
    domain.setDomain("google.com");
    domain.forwardPath(true);
    domain.setForward("google.in");
9
    response = service.processMessage(request);
10
11
12
13
    assertStatus(StatusCode.PermanentRedirect);
    assertLocation("google.in/index.html");
    assertStandardResponseHeader();
}

Note that since the request is an expensive object to build, I’m stubbing it out. While domain is more of a value object in this case, so its not stubbed/mocked out.

This test has a lot of noise. All the stubbing logic was just making this test very difficult to understand what was going on. So I extracted some methods to make it more meaningful and hide the stubbing logic.

1
2
3
4
5
6
7
@Test
public void permanentlyRedirectDomainsWithPath()  {
    setDomainToBeRedirected("google.com");
    setDestinationDomain("google.in");
    shouldRedirectWithPath();
    setHostName("google.com");
    setPath("/index.html");
8
    response = service.processMessage(request);
9
10
11
12
    assertStatus(StatusCode.PermanentRedirect);
    assertLocation("google.in/index.html");
    assertStandardResponseHeader();
}

Looking at this code it occurred to me that I could use Fluent Interfaces and make this really read like natural language. So finally I got:

1
2
3
4
@Test
public void permanentlyRedirectDomainsWithPath()  {
    request("google.com").withPath("/index.html");
    redirect("google.com").withPath().to("google.in");
5
    response = service.processMessage(request);
6
7
8
9
    assertStatus(StatusCode.PermanentRedirect);
    assertLocation("google.in/index.html");
    assertStandardResponseHeader();
}
10
11
12
13
14
private ThisTest request(String domainName) {
    when(request.hostName()).thenReturn(domainName);
    when(request.protocol()).thenReturn("HTTP/1.1");
    return this;
}
15
16
17
private void withPath(String path) {
    when(request.path()).thenReturn(path);
}
18
19
20
21
private ThisTest redirect(String domainName) {
    domain.setDomain(domainName);
    return this;
}
22
23
24
25
private ThisTest withPath() {
    domain.forwardPath(true);
    return this;
}
26
27
28
private void to(String domainName) {
    domain.setForward(domainName);
}

Finally after introducing Context Objects, I was able to make the code even more easier to read and understand:

1
2
3
4
5
6
7
8
9
@Test
public void redirectSubDomainsPermanently() {
    lets.assume("google.com").getsRedirectedTo("google.in").withPath();
    response = domainForwardingServer.process(requestFor("google.com/index.html"));
    lets.assertThat(response).contains(StatusCode.PermanentRedirect)
                             .location("google.in/index.html").protocol("HTTP/1.1")
                             .connectionStatus("close").contentType("text/html")
                             .serverName("Directi Server 2.0");
}

    Licensed under
Creative Commons License