如何使用HttpBuilder-ng

时间:2017-11-01 21:39:49

标签: rest groovy marshalling spock httpbuilder-ng

我正在为我的java应用程序构建一个groovy rest客户端,以用于测试自动化。我最初在httpBuilder中编写了该服务,但无法弄清楚如何解析响应。在非200响应中,我得到了一个异常,我可以捕获并断言该消息。找不到,坏请求等。更新后,我可以解析响应,但是当我得到非200响应时,它会尝试将其解析为我的对象,然后抛出无用的“missingProperty”异常。该文档展示了如何使用response.parser <CONTENT_TYPE>, { config, fs ->...}解析响应,以及如何使用response.success{fs -> ...}response.when(<CODE>){fs -> ...}分支状态代码,而不是如何仅为成功解析并使用不同的逻辑失败。我目前的代码如下:

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
}
然后我的spock测试:

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"
}

在旧版本下,第一次测试失败,第二次测试通过。在新版本下,第一次测试通过,第二次测试失败。我也从未真正看到request failed:...消息。我刚收到groovy.lang.MissingPropertyException。当我单步执行时,我可以看到它尝试将not found响应加载为Car对象。

额外奖励:为什么我必须使用显式属性映射而不是文档中的常规转换?

json(config, fs).collect { x -> x as Car }

更新 - 为了澄清,这不是我的实际来源。我正在使用在WAS上运行的专有内部API,我无法完全控制它。我正在编写API的业务逻辑,但使用WAS和我无法访问的专有库正在编组/解组响应。名称已被更改,以保护无辜/我的工作。这些是我从最初的帖子以来尝试过的解决方法: 这会在非200响应上正确触发故障块,但解析失败并出现IO - stream closed错误。此外,我在故障块中抛出的任何异常都被包装在RuntimeException中,这阻止了我访问信息。我已经尝试将它包装在文档中建议的传输异常中,但是当我得到它时它仍然是一个RuntimeException。

    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") }
            }
        }
    }
}

这一个正确解析了200个带有条目的响应,200个没有条目仍然抛出缺少的属性异常。与之前的impl一样,AutomationException被包装,因此无用。

    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")
            }
        }
    }
}

关于奖金,我所遵循的指南显示了json(config, fs)输出到Car对象的隐式转换。我必须明确设置新对象的道具。没什么大不了的,但是我想知道我是否错误地配置了其他东西。

1 个答案:

答案 0 :(得分:1)

您可以在failure处理程序中抛出异常,它将执行您要查找的内容:

response.failure { fs ->
    throw new IllegalStateException('No car found')
}

我不确定您要测试的服务器是什么,所以我使用Ersatz编写了一个测试:

import com.stehno.ersatz.ErsatzServer
import spock.lang.AutoCleanup
import spock.lang.Specification

import static com.stehno.ersatz.ContentType.APPLICATION_JSON

class CarClientSpec extends Specification {

    @AutoCleanup('stop')
    private final ErsatzServer server = new ErsatzServer()

    def 'successful get'() {
        setup:
        server.expectations {
            get('/cars/make/Toyota').responder {
                content '[{"make":"Toyota","model":"Corolla"}]', APPLICATION_JSON
            }
        }

        CarClient client = new CarClient(server.httpUrl)

        when:
        List<Car> cars = client.getCars('Toyota')

        then:
        cars.size() == 1
        cars.contains(new Car('Toyota', 'Corolla'))
    }

    def 'failed get'() {
        setup:
        server.expectations {
            get('/cars/make/Ford').responds().code(404)
        }

        CarClient client = new CarClient(server.httpUrl)

        when:
        client.getCars('Ford')

        then:
        def ex = thrown(IllegalStateException)
        ex.message == 'No car found'
    }
}

请注意,我必须让您的客户端具有可配置的基本网址(并且Car需要@Canonical注释)。如果您还没有阅读有关Take a REST with HttpBuilder-NG and Ersatz的博文,我建议您这样做,因为它提供了一个很好的概述。

我不确定你的奖金问题是什么意思。