Groovy HTTPBuilderを使用して、HTTP409ステータスメッセージとともに正しいエラーメッセージが本文に返されることを確認する統合テストを作成しようとしています。ただし、失敗した場合にHTTP応答の本文に実際にアクセスする方法がわかりません。
http.request(ENV_URL, Method.POST, ContentType.TEXT) {
uri.path = "/curate/${id}/submit"
contentType = ContentType.JSON
response.failure = { failresp_inner ->
failresp = failresp_inner
}
}
then:
assert failresp.status == 409
// I would like something like
//assert failresp.data == "expected error message"
サーバーからのHTTP応答は次のようになります。
2013-11-13 18:17:58,726 DEBUG wire - << "HTTP/1.1 409 Conflict[\r][\n]"
2013-11-13 18:17:58,726 DEBUG wire - << "Date: Wed, 13 Nov 2013 23:17:58 GMT[\r][\n]"
2013-11-13 18:17:58,726 DEBUG wire - << "Content-Type: text/plain[\r][\n]"
2013-11-13 18:17:58,726 DEBUG wire - << "Transfer-Encoding: chunked[\r][\n]"
2013-11-13 18:17:58,727 DEBUG wire - << "[\r][\n]"
2013-11-13 18:17:58,728 DEBUG wire - << "E[\r][\n]"
2013-11-13 18:17:58,728 DEBUG wire - << "expected error message"
2013-11-13 18:17:58,728 DEBUG wire - << "[\r][\n]"
2013-11-13 18:17:58,728 DEBUG wire - << "0[\r][\n]"
2013-11-13 18:17:58,728 DEBUG wire - << "[\r][\n]"
私は最近、Spockを使用してRESTエンドポイントを統合テストしようとしているときに、これに苦労していました。Samの回答をインスピレーションとして使用し、そのHttpBuilderの自動キャストを引き続き活用できるように改善しました。しばらくいじった後、どのステータスコードが返されるかに関係なく、動作を標準化するために、成功ハンドラークロージャーを失敗ハンドラーに割り当てるという素晴らしいアイデアがありました。
client.handler.failure = client.handler.success
実際の例:
...
import static org.Apache.http.HttpStatus.*
...
private RESTClient createClient(String username = null, String password = null) {
def client = new RESTClient(BASE_URL)
client.handler.failure = client.handler.success
if(username != null)
client.auth.basic(username, password)
return client
}
...
def unauthenticatedClient = createClient()
def userClient = createClient(USER_USERNAME, USER_PASSWORD)
def adminClient = createClient(ADMIN_USERNAME, ADMIN_PASSWORD)
...
def 'get account'() {
expect:
// unauthenticated tries to get user's account
unauthenticatedClient.get([path: "account/$USER_EMAIL"]).status == SC_UNAUTHENTICATED
// user gets user's account
with(userClient.get([path: "account/$USER_EMAIL"])) {
status == SC_OK
with(responseData) {
email == USER_EMAIL
...
}
}
// user tries to get user2's account
with(userClient.get([path: "account/$USER2_EMAIL"])) {
status == SC_FORBIDDEN
with(responseData) {
message.contains(USER_EMAIL)
message.contains(USER2_EMAIL)
...
}
}
// admin to get user's account
with(adminClient.get([path: "account/$USER_EMAIL"])) {
status == SC_OK
with(responseData) {
email == USER_EMAIL
...
}
}
}
次を使用すると機能しますか?
response.failure = { resp, reader ->
failstatus = resp.statusLine
failresp = reader.text
}
HttpBuilderを使い始めたときも、これに苦労しました。私が思いついた解決策は、HTTPBuilderの成功と失敗のクロージャーを定義して、次のような一貫した値を返すことでした。
HTTPBuilder http = new HTTPBuilder()
http.handler.failure = { resp, reader ->
[response:resp, reader:reader]
}
http.handler.success = { resp, reader ->
[response:resp, reader:reader]
}
このように定義すると、HTTPBuilderインスタンスは、responseオブジェクト(HttpResponseDecoratorのインスタンス)とreaderオブジェクトを含むマップを一貫して返します。その場合、リクエストは次のようになります。
def map = http.request(ENV_URL, Method.POST, ContentType.TEXT) {
uri.path = "/curate/${id}/submit"
contentType = ContentType.JSON
}
def response = map['response']
def reader = map['reader']
assert response.status == 409
リーダーは、応答本文へのアクセスを提供するある種のオブジェクトになります。応答本文のタイプは、getClass()メソッドを呼び出すことで判別できます。
println "reader type: ${reader.getClass()}"
リーダーオブジェクトのタイプは、応答のContent-Typeヘッダーによって決定されます。リクエストに「Accept」ヘッダーを追加することで、サーバーに具体的に何を返したいかを伝えることができます。