2.20HTTP 请求与响应断言(Assert)
在开发、测试中,经常需要对请求内容和响应结果进行验证,即“断言”。
在开发、测试中,经常需要对请求内容和响应结果进行验证,即“断言”。
系统将断言分为两类:
- 请求断言:在构建完
HttpRequestMessage后、发送前执行,用于校验请求的URI、方法、标头、主体等。 - 响应断言:在收到
HttpResponseMessage后执行,用于校验状态码、响应头、响应体、耗时等。
两类断言均通过 UseAssertions() 启用,并在 Asserts(configure) 中统一配置。若断言失败,会抛出 HttpAssertionException。
HttpRequestBuilder.Get("https://furion.net") .UseAssertions() .Asserts(ast => ast // 请求断言:发送前立即检查,失败不会发出请求 .RequestMethod(HttpMethod.Get) .RequestUri("https://furion.net/") // 响应断言:收到响应后检查 .ResponseStatusCode(200) .ResponseHeaderExists("encoding") );其中,ast 参数为 HttpAssertionBuilder 类型,内置了以下常用断言方法(支持自定义扩展):
请求断言方法(发送前执行)#
RequestUri(expectedUri):断言请求URI等于指定字符串- 失败时抛出:
Expected request URI to be '{expectedUri}', but found '{actual}'.
- 失败时抛出:
RequestMethod(expectedMethod):断言HTTP方法等于指定的HttpMethod- 失败时抛出:
Expected request method to be {expectedMethod}, but found {actual}.
- 失败时抛出:
RequestHeaderExists(name):断言指定的请求标头存在(包括内容标头)- 失败时抛出:
Expected request header '{name}' to exist, but it was not found.
- 失败时抛出:
RequestHeaderEquals(name, expectedValue):断言请求标头的第一个值严格等于指定字符串(区分大小写)- 失败时抛出:
Expected request header '{name}' to be '{expectedValue}', but found '{actual}'.
- 失败时抛出:
RequestHeaderContains(name, expectedValue):断言请求标头任意值包含指定子字符串(不区分大小写)- 失败时抛出:
Expected request header '{name}' to contain '{expectedValue}', but the header was not found.或Expected request header '{name}' to contain '{expectedValue}', but actual values were: [{...}].
- 失败时抛出:
RequestContentContains(expectedSubstring):断言请求内容包含指定子字符串(不区分大小写)- 失败时抛出:
Expected request content to contain '{expectedSubstring}', but it was not found.
- 失败时抛出:
RequestContentEquals(expected):断言请求内容完全等于指定字符串- 失败时抛出:
Expected request content to be '{expected}', but found '{actual}'.
- 失败时抛出:
RequestSatisfies(assertion):自定义请求断言(同步或异步),直接操作HttpRequestMessage- 异步重载接受
Func<HttpRequestMessage, Task>。
- 异步重载接受
响应断言方法(收到响应后执行)#
AddAssertion(assertion):添加自定义断言委托(默认视为响应断言),如ast.AddAssertion(async context => await ...)。ResponseStatusCode(statusCode):断言响应状态码等于指定值(整数或HttpStatusCode)- 失败时抛出:
Expected response status code to be {expected}, but found {actual}.
- 失败时抛出:
ResponseStatusCodeIn(allowedStatusCodes):断言状态码在允许列表中- 失败时抛出:
Expected response status code to be one of [{string.Join(", ", allowedStatusCodes)}], but found {actual}.
- 失败时抛出:
ResponseIsSuccessStatusCode():断言请求成功(状态码为2xx)- 失败时抛出:
Expected response to be successful (2xx status code), but found status code {(int)context.StatusCode}.
- 失败时抛出:
ResponseContentContains(expectedSubstring):断言响应内容包含指定子字符串(不区分大小写)- 失败时抛出:
Expected response content to contain '{expectedSubstring}', but it was not found.
- 失败时抛出:
ResponseContentEquals(expected):断言响应内容完全等于指定的字符串- 失败时抛出:
Expected response content to be '{expected}', but found '{content}'.
- 失败时抛出:
ResponseContentMatches(pattern):断言响应内容与指定的正则表达式匹配- 失败时抛出:
Expected response content to match regex '{pattern}', but it did not.
- 失败时抛出:
ResponseContentNotEmpty():断言响应内容不为空- 失败时抛出:
Expected response content not to be empty.
- 失败时抛出:
ResponseHeaderExists(name):断言指定响应头存在(包括内容头)- 失败时抛出:
Expected response header '{name}' to exist, but it was not found.
- 失败时抛出:
ResponseHeaderEquals(name, expectedValue):断言响应头的第一个值严格等于指定字符串(区分大小写)- 失败时抛出:
Expected response header '{name}' to be '{expectedValue}', but found '{actualValue}'.
- 失败时抛出:
ResponseHeaderContains(name, expectedValue):断言响应头任意值包含指定子字符串(不区分大小写)- 失败时抛出:
Expected response header '{name}' to contain '{expectedValue}', but the header was not found.或Expected response header '{name}' to contain '{expectedValue}', but actual values were: [{string.Join(", ", values)}].
- 失败时抛出:
ResponseHeaderNotExists(name):断言指定的响应标头不存在(包括内容头)- 失败时抛出:
Expected response header '{name}' not to exist, but it was found.
- 失败时抛出:
ResponseDurationUnder(maxMilliseconds):断言请求耗时低于指定毫秒数- 失败时抛出:
Expected response duration to be under {maxDuration.TotalMilliseconds:F2}ms, but it took {actualDuration.TotalMilliseconds:F2}ms.
- 失败时抛出:
ResponseSatisfies(assertion):自定义响应断言(同步或异步),直接操作HttpResponseMessage- 异步重载接受
Func<HttpResponseMessage, Task>。
- 异步重载接受
自定义断言方法#
除了内置方法,你还可以通过扩展方法为 HttpAssertionBuilder 添加自定义断言逻辑,以减少重复代码并提升可读性。例如,实现一个 ResponseIsJson 方法,用于验证响应内容是否为 application/json 类型:
public static class HttpAssertionBuilderExtensions{ public static HttpAssertionBuilder ResponseIsJson(this HttpAssertionBuilder httpAssertionBuilder) { return httpAssertionBuilder.AddAssertion(async context => { var contentType = context.ResponseMessage?.Content?.Headers.ContentType?.MediaType; const string jsonMediaType = "application/json"; // 允许 "application/json" 或 "application/json; charset=utf-8" 等 if (string.IsNullOrEmpty(contentType) || !contentType.StartsWith(jsonMediaType, StringComparison.OrdinalIgnoreCase)) { await HttpAssertionException.ThrowAsync( $"Expected response Content-Type to be '{jsonMediaType}' (or a subtype with parameters), but found '{contentType}'."); } }); }}使用自定义方法示例:
HttpRequestBuilder.Get("https://furion.net") .UseAssertions() .Asserts(ast => ast.ResponseIsJson().ResponseStatusCode(200)); // 支持链式调用借助 C# 扩展方法,你可以灵活扩展 HttpAssertionBuilder 的功能,提升代码的可维护性和复用性。