1

我正在为我的 java 应用程序构建一个 groovy rest 客户端,以用于测试自动化。我最初在 httpBuilder 中编写了服务,但不知道如何解析响应。在非 200 响应中,我得到了一个异常,我可以在消息中捕获并断言。未找到、错误请求等。更新后,我可以解析响应,但每当我收到非 200 响应时,它都会尝试将其解析为我的对象,然后抛出一个无用的“missingProperty”异常。该文档显示了如何使用 , 解析响应response.parser <CONTENT_TYPE>, { config, fs ->...},以及如何使用response.success{fs -> ...}, or对状态代码进行分支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 对象。我必须明确设置新对象的道具。没什么大不了的,但这让我想知道我是否错误地配置了其他东西。

4

1 回答 1

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

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

我不确定你在奖金问题中的意思。

于 2017-11-02T13:08:59.043 回答