I want the Unit Test to go through both, Success and Failure execution paths. How to make the test case to go to Success or Failure path?
void addRespondents()
{
http.request(POST, TEXT) {
uri.path = PATH
headers.Cookie = novaAuthentication
headers.Accept = 'application/json'
headers.ContentType = 'application/json'
body = respondentString
response.success = { resp, json ->
statusCode = 2
}
response.failure = { resp, json ->
if(resp.status == 400) {
statusCode = 3
def parsedJson = new JsonSlurper().parse(json)
}else{
autoCreditResponse = createErrorResponse(resp)
}
}
}
}
OK, it seems you use this library:
<dependency>
<groupId>org.codehaus.groovy.modules.http-builder</groupId>
<artifactId>http-builder</artifactId>
<version>0.7.1</version>
</dependency>
Because I never used HTTPBuilder before and it looks like a nice tool when using Groovy, I played around with it a bit, replicating your use case, but converting it into a full MCVE. I have to admit that testability for this library is in bad shape. Even the tests for the library itself are no proper unit tests, but rather integration tests, actually performing network requests instead of mocking them. The tool itself also contains to test mocks or hints about how to test.
Because the functionality heavily relies on dynamically binding variables in closures, mock-testing is somewhat ugly and I had to look into the tool's source code in order to pull it off. Nice black-box testing is basically impossible, but here is how you can inject a mock HTTP client returning a predefined mock response which contains enough information not to derail the application code:
As you can see, I added enough data in the class to be able to run it and do something meaningful. The fact that your method returns void
instead of a testable result and we only have to rely on testing side effects, does not make testing easier.
package de.scrum_master.stackoverflow.q68093910
import groovy.json.JsonSlurper
import groovyx.net.http.HTTPBuilder
import groovyx.net.http.HttpResponseDecorator
import static groovyx.net.http.ContentType.TEXT
import static groovyx.net.http.Method.POST
class JsonApiClient {
HTTPBuilder http = new HTTPBuilder("https://jsonplaceholder.typicode.com")
String PATH = "/users"
String novaAuthentication = ''
String respondentString = ''
String autoCreditResponse = ''
int statusCode
JsonSlurper jsonSlurper = new JsonSlurper()
void addRespondents() {
http.request(POST, TEXT) {
uri.path = PATH
headers.Cookie = novaAuthentication
headers.Accept = 'application/json'
headers.ContentType = 'application/json'
body = respondentString
response.success = { resp, json ->
println "Success -> ${jsonSlurper.parse(json)}"
statusCode = 2
}
response.failure = { resp, json ->
if (resp.status == 400) {
println "Error 400 -> ${jsonSlurper.parse(json)}"
statusCode = 3
}
else {
println "Other error -> ${jsonSlurper.parse(json)}"
autoCreditResponse = createErrorResponse(resp)
}
}
}
}
String createErrorResponse(HttpResponseDecorator responseDecorator) {
"ERROR"
}
}
This spec covers all 3 cases for responses in the above code, using an unrolled test which returns different status codes.
Because the method under test returns void
, I decided to verify the side effect that HTTPBuilder.request
was actually called. In order to do this, I had to use a Spy
on the HTTPBuilder
. Testing for this side effect is optional, then you do not need the spy.
package de.scrum_master.stackoverflow.q68093910
import groovyx.net.http.HTTPBuilder
import org.apache.http.HttpResponse
import org.apache.http.client.HttpClient
import org.apache.http.client.ResponseHandler
import org.apache.http.entity.StringEntity
import org.apache.http.message.BasicHttpResponse
import org.apache.http.message.BasicStatusLine
import spock.lang.Specification
import spock.lang.Unroll
import static groovyx.net.http.ContentType.TEXT
import static groovyx.net.http.Method.POST
import static org.apache.http.HttpVersion.HTTP_1_1
class JsonApiClientTest extends Specification {
@Unroll
def "verify status code #statusCode"() {
given: "a JSON response"
HttpResponse response = new BasicHttpResponse(
new BasicStatusLine(HTTP_1_1, statusCode, "my reason")
)
def json = "{ \"name\" : \"JSON-$statusCode\" }"
response.setEntity(new StringEntity(json))
and: "a mock HTTP client returning the JSON response"
HttpClient httpClient = Mock() {
execute(_, _ as ResponseHandler, _) >> { List args ->
(args[1] as ResponseHandler).handleResponse(response)
}
}
and: "an HTTP builder spy using the mock HTTP client"
HTTPBuilder httpBuilder = Spy(constructorArgs: ["https://foo.bar"])
httpBuilder.setClient(httpClient)
and: "a JSON API client using the HTTP builder spy"
def builderUser = new JsonApiClient(http: httpBuilder)
when: "calling 'addRespondents'"
builderUser.addRespondents()
then: "'HTTPBuilder.request' was called as expected"
1 * httpBuilder.request(POST, TEXT, _)
where:
statusCode << [200, 400, 404]
}
}
If you have used Spock for a while, probably I do not need to explain much. If you are a Spock or mock testing beginner, probably this is a bit too complex. But FWIW, I hope that if you study the code, you can wrap your head around how I did it. I tried to use Spock label comments in order to explain it.
The console log indicates that all 3 execution paths are covered by the specification:
Success -> [name:JSON-200]
Error 400 -> [name:JSON-400]
Other error -> [name:JSON-404]
If you use a code coverage tool, of course you do not need the log statements I inserted into the application code. They are just for demonstration purposes.
http.request(POST, TEXT) {...}
In order to circumvent the fact that your method returns void
, you can save the result of HTTPBuilder.request(..)
by stubbing the method call in the spy interaction, passing through the original result at first, but also checking for the expected result.
Simply add def actualResult
somewhere in the given ... and
blocks (in when
it is too late), then assign the result of callRealMethod()
to it and then compare to expectedResult
like this:
and: "a JSON API client using the HTTP builder spy"
def builderUser = new JsonApiClient(http: httpBuilder)
def actualResult
when: "calling 'addRespondents'"
builderUser.addRespondents()
then: "'HTTPBuilder.request' was called as expected"
1 * httpBuilder.request(POST, TEXT, _) >> {
actualResult = callRealMethod()
}
actualResult == expectedResult
where:
statusCode << [200, 400, 404]
expectedResult << [2, 3, "ERROR"]
If you prefer a data table instead of data pipes, the where
block looks like this:
where:
statusCode | expectedResult
200 | 2
400 | 3
404 | "ERROR"
I think this pretty much covers all that makes sense to test here.