Writing a Spock extension for unit-testing http / rest client code

Spock is an awesome testing framework. It's written almost entirely in Java, while Spock tests them self are written in Groovy.

Some days ago I wanted to unit test some http client code for a Grails plugin I'm working on. This inspired me to have a look at Spock's extension API. The API is very good and easy to understand! All you have to do in order to write an annotation driven extension is to implement an annotation, method interceptor and an extension class connection everything together.

Example from the Grails sitemap plugin

Most search engines exposes a service where you can send a "ping" when your sitemap has been updated. These are usually invoked by sending a simple http request. Below is test code from the sitemap plugin.

@WithHttpServer(port=23456)
def "pinging dummy search engine works"() {
      
  given: "mocked http server"
    TestHttpServer.mock = Mock(HttpServer)
   
  and: "register url for a dummy service to ping"
    String base = "http://localhost:23456"
    String pingUri = base + "/searchEngine/ping?sitemap=%s"
    searchEnginePinger.addPingUrl "dummy", pingUri
     
  when: "ping all registered services"
    boolean allSuccess = searchEnginePinger.pingAll()
      
  then: "one ping request for with the expected sitemap url"
    1 * TestHttpServer.mock.request("get", "/searchEngine/ping", 
      {it["sitemap"] == "http://localhost:8080/sitemap.xml"}, _) >> "ok"
      
   and: "it should return true if every ping returned http 200"
     allSuccess == true
   
}

Pros and cons

Pros: What I really like about it is that setup and tear down of the http server is handled outside the actual test code. Another really neat feature is that it allows use of Spock's interaction API.

Cons: Error messages could be a lot more helpful. The API for specifying constraints on parameters and headers could be more readable.

The source code is as usual hosted at GitHub.

User comments

Gravatar

Peter Niederwieser - 03. Dec 2010 01:26

Well done! One way to avoid the "ugly" (having to pass the mock to the HTTP server) is to use a field annotation instead of a method annotation: class MySpec extends Specification { @HttpServer(port=23456) RequestHandler handler = Mock() } If the extension passes the FieldInfo to the interceptor, the latter can get hold of the mock with FieldInfo.readValue(). A drawback of this approach is that the extension now needs to intercept (and provide its service to) all feature methods. If this is not what you want, you could combine the two approaches (method annotation together with a mock field).

Gravatar

Kim Betti - 03. Dec 2010 12:06

Excellent suggestion! I've re-factored the code and updated the example on the GitHub page. Starting the http server for each feature method doesn't sound like such a drawback. The server startup time is negligible and the the other feature methods are likely to require the server as well since they're grouped in the same spec.


Fork me on GitHub