I'm building a groovy rest client for my java app to use with test automation. I originally wrote the service in httpBuilder, but couldn't figure out how to parse the response. On non-200 responses, I got an exception which I could catch and assert on the message. Not found, bad request, etc. After updating, I can parse the response, but anytime I get a non-200 response, it tries to parse it as my object which then throws a useless 'missingProperty' exception. The doc shows how to parse the response using response.parser <CONTENT_TYPE>, { config, fs ->...}
, and how to branch on the status code using response.success{fs -> ...}
, or response.when(<CODE>){fs -> ...}
, but not how to parse only for success and use different logic for failure. My current code is as follows:
import groovyx.net.http.ChainedHttpConfig
import groovyx.net.http.FromServer
import groovyx.net.http.HttpBuilder
import groovyx.net.http.NativeHandlers
import static groovyx.net.http.ContentTypes.JSON
import static groovyx.net.http.NativeHandlers.Parsers.json
class CarClient {
private final HttpBuilder http
CarClient() {
http = HttpBuilder.configure {
request.uri = "localhost:8080"
request.encoder JSON, NativeHandlers.Encoders.&json
}
}
List<Car> getCars(make) {
http.get(List) {
request.uri.path = "/cars/make/${make}"
response.failure { fs ->
println("request failed: ${fs}")
}
response.parser JSON, { ChainedHttpConfig config, FromServer fs ->
json(config, fs).collect { x -> new Car(make: x."make", model: x."model") }
}
}
}
}
class Car {
def make
def model
}
then my spock tests:
def "200 response should return list of cars"() {
when:
def result = client.getCars("honda")
then:
result.size == 3
result[0].make == "honda"
result[0].model == "accord"
}
def "404 responses should throw exception with 'not found'"() {
when:
client.getCars("ford")
then:
final Exception ex = thrown()
ex.message == "Not Found"
}
Under the old version the first test failed, and the second test passed. Under new version, the first test passes and the second test fails. I never actually see the request failed:...
message, either. i just get a groovy.lang.MissingPropertyException
. when I step through, I can see it trying to load the not found
response as a Car object.
Bonus: why do i have to use explicit property mappings instead of groovy casting like in the doc?
json(config, fs).collect { x -> x as Car }
update - For clarification, this isn't my actual source. I'm hitting a proprietary internal API running on WAS, which I don't fully control. I am writing the API's business logic, but response is being marshalled/unmarshalled using WAS and proprietary libraries to which I do not have access. Names have been changed to protect the innocent/my job. These are the workarounds I've tried since initial post:
This triggers the failure block correctly on non-200 responses, but the parsing fails with IO - stream closed
error. Also, any exceptions i throw in the failure block get wrapped in a RuntimeException which prevents me from accessing the info. I've tried wrapping it in a transport exception as suggested in the doc, but it's still a RuntimeException by the time i get it.
List<Car> getCars(make) {
http.get(List) {
request.uri.path = "/cars/make/${make}"
response.failure { fs ->
println("request failed: ${fs}")
throw new AutomationException("$fs.statusCode : $fs.message")
}
response.success { FromServer fs ->
new JsonSlurper().parse(new InputStreamReader(fs.getInputStream, fs.getCharset())).collect { x -> new Car(make: x."make", model: x."model") }
}
}
}
}
This one parses correctly on 200 responses with entries, 200's with no entries still throw missing property exceptions. Like the previous impl, the AutomationException is wrapped and therefor not useful.
List<Car> getCars(make) {
http.get(List) {
request.uri.path = "/cars/make/${make}"
response.parser JSON, { ChainedHttpConfig config, FromServer fs ->
if (fs.statusCode == 200) {
json(config, fs).collect { x -> new Car(make: x."make", model: x."model") }
} else {
throw new AutomationException("$fs.statusCode : $fs.message")
}
}
}
}
Regarding the bonus, the guide I was following showed implicit casting of the json(config, fs)
output to the Car object. I have to explicitly set the props of the new object. Not a big deal, but it makes me wonder if I've configured something else incorrectly.