Docker Java Example Series
Spock with Full Tomcat Server
This is the approach I am most familiar with.https://github.com/ryanmckaytx/java-docker-example/blob/v0.2/src/test/groovy/net/ryanmckay/demo/GreetingControllerSpec.groovy
Timing
I ran and timed the test in isolation with$ ./gradlew test --tests '*GreetingControllerSpec' --profile
Total 'test' task time (reported by gradle profile output): 13.734s
Total test run time (reported by junit test output): 12.690s
Time to start GreetingControllerSpec (load full context and start tomcat): 12.157s
So, not fast. Maybe one of the other approaches can do better.
Test Code Readability
def "no Param greeting should return default message"() { when: ResponseEntity<Greeting> responseGreeting = restTemplate .getForEntity("http://localhost:" + port + "/greeting", Greeting.class) then: responseGreeting.statusCode == HttpStatus.OK responseGreeting.body.content == "blah" }I really like Spock. I like the plain English test names. I like the separate sections for given, when, then, etc. I think it reads well and makes it obvious what is under test.
Test Output Readability
When a test fails, you want to see why, right? In this aspect, groovy power assertions are simply unparalleled.
Condition not satisfied: responseGreeting.body.content == "blah" | | | | | | | false | | | 12 differences (7% similarity) | | | (He)l(lo, World!) | | | (b-)l(ah--------) | | Hello, World! | Greeting(id=1, content=Hello, World!) <200 OK,Greeting(id=1, content=Hello, World!),{Content-Type=[application/json;charset=UTF-8], Transfer-Encoding=[chunked], Date=[Tue, 22 Aug 2017 22:06:33 GMT]}> at net.ryanmckay.demo.GreetingControllerSpec.no Param greeting should return default message(GreetingControllerSpec.groovy:27)
Spock with MockMvc
By adding @AutoConfigureMockMvc to your test class, you can inject a MockMvc instance, which facilitates making calls directly to Springs HTTP request handling layer. This allows you to skip starting up a Tomcat server, so should save some time and/or memory. On the other hand, you are testing less of the round trip, so the time savings would need to be significant to justify this approach.
https://github.com/ryanmckaytx/java-docker-example/blob/v0.2/src/test/groovy/net/ryanmckay/demo/GreetingControllerMockMvcSpec.groovy
https://github.com/ryanmckaytx/java-docker-example/blob/v0.2/src/test/groovy/net/ryanmckay/demo/GreetingControllerMockMvcSpec.groovy
Timing
This approach was about 500ms faster than with tomcat. Not significant enough to justify for me, considering the overall time scale.
Total 'test' task time (reported by gradle profile output): 13.263s
Total test run time (reported by junit test output): 12.281s
Time to start GreetingControllerSpec (load full context, no tomcat): 11.804s
Total 'test' task time (reported by gradle profile output): 13.263s
Total test run time (reported by junit test output): 12.281s
Time to start GreetingControllerSpec (load full context, no tomcat): 11.804s
Test Code Readability
def "no Param greeting should return default message"() { when: def resultActions = mockMvc.perform(get("/greeting")).andDo(print()) then: resultActions .andExpect(status().isOk()) .andExpect(jsonPath('$.content').value("blah")) }
This reads reasonably well. Capturing the resultActions in the when block to use later in the then block is a little awkward, but not too bad. Being able to express arbitrary JSON path expectations is convenient. I didn't see an obvious way to get a ResponseEntity as was done in the full Tomcat example.
Test Output Readability
Condition failed with Exception: resultActions .andExpect(status().isOk()) .andExpect(jsonPath('$.content').value("blah")) | | | | | | | | | | | | | org.springframework.test.web.servlet.result.JsonPathResultMatchers$2@1f977413 | | | | | org.springframework.test.web.servlet.result.JsonPathResultMatchers@6cd50e89 | | | | java.lang.AssertionError: JSON path "$.content" expected:<blah> but was:<Hello, World!&rt; | | | org.springframework.test.web.servlet.result.StatusResultMatchers$10@660dd332 | | org.springframework.test.web.servlet.result.StatusResultMatchers@251379e8 | org.springframework.test.web.servlet.MockMvc$1@68837646 org.springframework.test.web.servlet.MockMvc$1@68837646 at net.ryanmckay.demo.GreetingControllerMockMvcSpec.no Param greeting should return default message(GreetingControllerMockMvcSpec.groovy:29) Caused by: java.lang.AssertionError: JSON path "$.content" expected:<blah> but was:<Hello, World!&rt;
This test output does not read well at all. Spock and the Spring MockMvc library are both tripping over each other trying to provide verbose output. I think you need choose either Spock or MockMvc, but not both.
JUnit with WebMvcTest and MockMvc
This configuration is on the far other end of the spectrum from full service Spock. With @WebMvcTest, not only does it not start a Tomcat server, it doesn't even load a full context. In the current state of the project this doesn't make much of a difference because the GreetingController has no injected dependencies. If it did, I would have to mock those out. Again, because of the differences from "real" configuration, time savings would need to be significant.
https://github.com/ryanmckaytx/java-docker-example/blob/v0.2/src/test/groovy/net/ryanmckay/demo/GreetingControllerTests.java
https://github.com/ryanmckaytx/java-docker-example/blob/v0.2/src/test/groovy/net/ryanmckay/demo/GreetingControllerTests.java
Timing
This approach was also about 500ms faster overall than full context with Tomcat.
Total 'test' task time (reported by gradle profile output): 13.275s
Total test run time (reported by junit test output): 0.269s
Time to start GreetingControllerSpec (load narrow context, no tomcat): 11.88s
Total 'test' task time (reported by gradle profile output): 13.275s
Total test run time (reported by junit test output): 0.269s
Time to start GreetingControllerSpec (load narrow context, no tomcat): 11.88s
Test Code Readability
@Testpublic void noParamGreetingShouldReturnDefaultMessage() throws Exception { this.mockMvc.perform(get("/greeting")).andDo(print()).andExpect(status().isOk()) .andExpect(jsonPath("$.content").value("blah")); }
This is the least readable for me. Again, I like separating the call under test from the assertions.
Test Output Readability
The failure message for MockMvc-based assertion failures isn't as informative as Spock in this case.
java.lang.AssertionError: JSON path "$.content" expected:<blah> but was:<Hello, World!>" type="java.lang.AssertionError">java.lang.AssertionError: JSON path "$.content" expected:<blah> but was:<Hello, World!>
Because the test called .andDo(print()), some additional information is available in the standard out of the test, including the full response status code and body.
Conclusion
I'm as convinced as ever that Spock is the premier Java testing framework. I'm reserving judgment on the Spring annotations that let you avoid starting a Tomcat server or load the full context. If the project gets more complicated, those could potentially provide a nice speedup.I tagged the code repo at v0.2 at this point.