2.20HTTP Request and Response Assertions (Assert)

Created on Aug 17, 2026~16 min read

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 HttpRequestMessage is built and before it is sent, used to validate the request's URI, method, headers, body, and so on.
  • Response assertions: executed after HttpResponseMessage is 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.

cs
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 request URI equals the specified string
    • Thrown on failure: Expected request URI to be '{expectedUri}', but found '{actual}'.
  • RequestMethod(expectedMethod): asserts that the HTTP method equals the specified HttpMethod
    • Thrown on failure: Expected request method to be {expectedMethod}, but found {actual}.
  • 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.
  • 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}'.
  • 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. or Expected request header '{name}' to contain '{expectedValue}', but actual values were: [{...}].
  • 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.
  • RequestContentEquals(expected): asserts that the request content exactly equals the specified string
    • Thrown on failure: Expected request content to be '{expected}', but found '{actual}'.
  • RequestSatisfies(assertion): custom request assertion (synchronous or asynchronous) that directly operates on HttpRequestMessage
    • The asynchronous overload accepts Func<HttpRequestMessage, Task>.

Response Assertion Methods (Executed After Receiving a Response)

  • AddAssertion(assertion): Adds a custom assertion delegate (treated as a response assertion by default), such as ast.AddAssertion(async context => await ...).
  • ResponseStatusCode(statusCode): Asserts that the response status code equals the specified value (an integer or HttpStatusCode)
    • Throws on failure: Expected response status code to be {expected}, but found {actual}.
  • 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}.
  • ResponseIsSuccessStatusCode(): Asserts that the request succeeded (status code is 2xx)
    • Throws on failure: Expected response to be successful (2xx status code), but found status code {(int)context.StatusCode}.
  • 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.
  • ResponseContentEquals(expected): Asserts that the response content exactly equals the specified string
    • Throws on failure: Expected response content to be '{expected}', but found '{content}'.
  • 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.
  • ResponseContentNotEmpty(): Asserts that the response content is not empty
    • Throws on failure: Expected response content not to be empty.
  • 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.
  • 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}'.
  • 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. or Expected response header '{name}' to contain '{expectedValue}', but actual values were: [{string.Join(", ", values)}].
  • 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.
  • 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.
  • ResponseSatisfies(assertion): A custom response assertion (synchronous or asynchronous) that directly operates on HttpResponseMessage
    • The asynchronous overload accepts Func<HttpResponseMessage, Task>.

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:

cs
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:

cs
HttpRequestBuilder.Get("https://furion.net")    .UseAssertions()    .Asserts(ast => ast.ResponseIsJson().ResponseStatusCode(200));      // Supports chained calls

With C# extension methods, you can flexibly extend the functionality of HttpAssertionBuilder, improving the maintainability and reusability of your code.