2.20HTTP Request and Response Assertions (Assert)
During development and testing, you often need to validate the request content and response result — that is, "assertions".
The system divides assertions into two categories:
- Request assertions: executed after
HttpRequestMessageis built and before it is sent, used to validate the request'sURI, method, headers, body, and so on. - Response assertions: executed after
HttpResponseMessageis received, used to validate the status code, response headers, response body, elapsed time, and so on.
Both types of assertions are enabled through UseAssertions() and configured uniformly in Asserts(configure). If an assertion fails, an HttpAssertionException is thrown.
HttpRequestBuilder.Get("https://furion.net") .UseAssertions() .Asserts(ast => ast // Request assertion: checked immediately before sending; the request is not sent on failure .RequestMethod(HttpMethod.Get) .RequestUri("https://furion.net/") // Response assertion: checked after the response is received .ResponseStatusCode(200) .ResponseHeaderExists("encoding") );Here, the ast parameter is of type HttpAssertionBuilder, which provides the following common assertion methods (custom extensions are supported):
Request Assertion Methods (Executed Before Sending)
RequestUri(expectedUri): asserts that the requestURIequals the specified string- Thrown on failure:
Expected request URI to be '{expectedUri}', but found '{actual}'.
- Thrown on failure:
RequestMethod(expectedMethod): asserts that theHTTPmethod equals the specifiedHttpMethod- Thrown on failure:
Expected request method to be {expectedMethod}, but found {actual}.
- Thrown on failure:
RequestHeaderExists(name): asserts that the specified request header exists (including content headers)- Thrown on failure:
Expected request header '{name}' to exist, but it was not found.
- Thrown on failure:
RequestHeaderEquals(name, expectedValue): asserts that the first value of the request header strictly equals the specified string (case-sensitive)- Thrown on failure:
Expected request header '{name}' to be '{expectedValue}', but found '{actual}'.
- Thrown on failure:
RequestHeaderContains(name, expectedValue): asserts that any value of the request header contains the specified substring (case-insensitive)- Thrown on failure:
Expected request header '{name}' to contain '{expectedValue}', but the header was not found.orExpected request header '{name}' to contain '{expectedValue}', but actual values were: [{...}].
- Thrown on failure:
RequestContentContains(expectedSubstring): asserts that the request content contains the specified substring (case-insensitive)- Thrown on failure:
Expected request content to contain '{expectedSubstring}', but it was not found.
- Thrown on failure:
RequestContentEquals(expected): asserts that the request content exactly equals the specified string- Thrown on failure:
Expected request content to be '{expected}', but found '{actual}'.
- Thrown on failure:
RequestSatisfies(assertion): custom request assertion (synchronous or asynchronous) that directly operates onHttpRequestMessage- The asynchronous overload accepts
Func<HttpRequestMessage, Task>.
- The asynchronous overload accepts
Response Assertion Methods (Executed After Receiving a Response)
AddAssertion(assertion): Adds a custom assertion delegate (treated as a response assertion by default), such asast.AddAssertion(async context => await ...).ResponseStatusCode(statusCode): Asserts that the response status code equals the specified value (an integer orHttpStatusCode)- Throws on failure:
Expected response status code to be {expected}, but found {actual}.
- Throws on failure:
ResponseStatusCodeIn(allowedStatusCodes): Asserts that the status code is in the allowed list- Throws on failure:
Expected response status code to be one of [{string.Join(", ", allowedStatusCodes)}], but found {actual}.
- Throws on failure:
ResponseIsSuccessStatusCode(): Asserts that the request succeeded (status code is2xx)- Throws on failure:
Expected response to be successful (2xx status code), but found status code {(int)context.StatusCode}.
- Throws on failure:
ResponseContentContains(expectedSubstring): Asserts that the response content contains the specified substring (case-insensitive)- Throws on failure:
Expected response content to contain '{expectedSubstring}', but it was not found.
- Throws on failure:
ResponseContentEquals(expected): Asserts that the response content exactly equals the specified string- Throws on failure:
Expected response content to be '{expected}', but found '{content}'.
- Throws on failure:
ResponseContentMatches(pattern): Asserts that the response content matches the specified regular expression- Throws on failure:
Expected response content to match regex '{pattern}', but it did not.
- Throws on failure:
ResponseContentNotEmpty(): Asserts that the response content is not empty- Throws on failure:
Expected response content not to be empty.
- Throws on failure:
ResponseHeaderExists(name): Asserts that the specified response header exists (including content headers)- Throws on failure:
Expected response header '{name}' to exist, but it was not found.
- Throws on failure:
ResponseHeaderEquals(name, expectedValue): Asserts that the first value of the response header strictly equals the specified string (case-sensitive)- Throws on failure:
Expected response header '{name}' to be '{expectedValue}', but found '{actualValue}'.
- Throws on failure:
ResponseHeaderContains(name, expectedValue): Asserts that any value of the response header contains the specified substring (case-insensitive)- Throws on failure:
Expected response header '{name}' to contain '{expectedValue}', but the header was not found.orExpected response header '{name}' to contain '{expectedValue}', but actual values were: [{string.Join(", ", values)}].
- Throws on failure:
ResponseHeaderNotExists(name): Asserts that the specified response header does not exist (including content headers)- Throws on failure:
Expected response header '{name}' not to exist, but it was found.
- Throws on failure:
ResponseDurationUnder(maxMilliseconds): Asserts that the request duration is under the specified number of milliseconds- Throws on failure:
Expected response duration to be under {maxDuration.TotalMilliseconds:F2}ms, but it took {actualDuration.TotalMilliseconds:F2}ms.
- Throws on failure:
ResponseSatisfies(assertion): A custom response assertion (synchronous or asynchronous) that directly operates onHttpResponseMessage- The asynchronous overload accepts
Func<HttpResponseMessage, Task>.
- The asynchronous overload accepts
Custom Assertion Methods
In addition to the built-in methods, you can add custom assertion logic to HttpAssertionBuilder via extension methods to reduce duplicated code and improve readability. For example, implement a ResponseIsJson method to verify whether the response content is of type 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"; // Allows "application/json" or "application/json; charset=utf-8", etc. 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}'."); } }); }}Example of using the custom method:
HttpRequestBuilder.Get("https://furion.net") .UseAssertions() .Asserts(ast => ast.ResponseIsJson().ResponseStatusCode(200)); // Supports chained callsWith C# extension methods, you can flexibly extend the functionality of HttpAssertionBuilder, improving the maintainability and reusability of your code.