Skip to main content

aweXpect.Web

Nuget

aweXpect.Web contains expectations for HttpRequestMessage, HttpResponseMessage and Uri, e.g. to verify the responses of an HttpClient.

Overview​

Expectations for HttpRequestMessage:

ExpectationNegatedSummary
HasMethodhas the expected HTTP method
HasRequestUrihas the expected request URI
HasHeaderDoesNotHaveHeaderhas a header, optionally with a value
HasContenthas the expected string content

Expectations for HttpResponseMessage:

ExpectationNegatedSummary
HasStatusCodeHasStatusCode().DifferentTohas the expected status code or status category
HasHeaderDoesNotHaveHeaderhas a header, optionally with a value
HasContentTypehas the expected media type
HasContenthas the expected string content
HasProblemDetailsContenthas a ProblemDetails content
HasRequestMessagethe request message meets nested expectations

Expectations for Uri:

ExpectationNegatedSummary
IsAbsoluteIsNotAbsolutean absolute URI
IsFileIsNotFilea file URI
IsLoopbackIsNotLoopbackreferences the local host
IsUncIsNotUnca UNC path
HasDefaultPortDoesNotHaveDefaultPortuses the default port of its scheme
HasSchemenegated comparisonhas the expected scheme

When an expectation on a request or response fails, the failure message includes the formatted HTTP request and response.

HttpRequestMessage​

Method​

You can verify that the HttpRequestMessage has the expected method:

HttpRequestMessage request = new(HttpMethod.Get, "https://music.example.com/tracks/1");

await Expect.That(request).HasMethod(HttpMethod.Get);

Request URI​

You can verify that the HttpRequestMessage has the expected request URI:

HttpRequestMessage request = new(HttpMethod.Get, "https://music.example.com/tracks/1");

await Expect.That(request).HasRequestUri("https://music.example.com/tracks/1");
await Expect.That(request).HasRequestUri(new Uri("https://music.example.com/tracks/1"));

Request headers​

You can verify that the HttpRequestMessage has a header, optionally with the expected value, or that it does not have it:

HttpRequestMessage request = new(HttpMethod.Get, "https://music.example.com/tracks/1");
request.Headers.Add("Accept", "application/json");

await Expect.That(request).HasHeader("Accept");
await Expect.That(request).HasHeader("Accept").WithValue("application/json");
await Expect.That(request).DoesNotHaveHeader("Authorization");

WithValue supports the same string options and match types as comparing strings.

You can also add expectations on the header value or on all of its values:

HttpRequestMessage request = new(HttpMethod.Get, "https://music.example.com/tracks/1");
request.Headers.Add("Accept-Encoding", ["gzip", "deflate"]);

await Expect.That(request).HasHeader("Accept-Encoding")
.WhoseValues(values => values.Contains("gzip"));

The failure message shows the difference and the request:

HttpRequestMessage request = new(HttpMethod.Get, "https://music.example.com/tracks/1");
request.Headers.Add("Cache-Control", "no-cache");

await Expect.That(request).HasHeader("Cache-Control").WithValue("max-age=0");
Failure message
Expected that request
has a "Cache-Control" header whose value is equal to "max-age=0",
but it had header value "no-cache", which differs at index 0:
↓ (actual)
"no-cache"
"max-age=0"
↑ (expected)

HTTP-Request:
GET https://music.example.com/tracks/1 HTTP/1.1
Cache-Control: no-cache

Request content​

You can verify that the HttpRequestMessage has the expected string content:

HttpRequestMessage request = new(HttpMethod.Post, "https://music.example.com/tracks")
{
Content = new StringContent("{\"title\": \"Let It Be\"}"),
};

await Expect.That(request).HasContent("*Let It Be*").AsWildcard();
await Expect.That(request).HasContent(content => content.Contains("Let It Be"));

The content supports the same string options and match types as comparing strings.

HttpResponseMessage​

Status code​

You can verify the status code of the HttpResponseMessage, either the exact value or its category:

HttpResponseMessage response = await httpClient.GetAsync("https://music.example.com/tracks/1");

await Expect.That(response).HasStatusCode(HttpStatusCode.OK);
await Expect.That(response).HasStatusCode().EqualTo(HttpStatusCode.OK);
await Expect.That(response).HasStatusCode().DifferentTo(HttpStatusCode.NotFound);
await Expect.That(response).HasStatusCode().Success();

The categories are Success() (2xx), Redirection() (3xx), ClientError() (4xx), ServerError() (5xx) and Error() (4xx or 5xx). Combine them with .Or to allow several:

HttpResponseMessage response = await httpClient.PostAsync("https://music.example.com/tracks", new StringContent(""));

await Expect.That(response).HasStatusCode().ClientError().Or.HasStatusCode().ServerError();

A failing expectation against a test server

HttpResponseMessage response = await httpClient.GetAsync("/tracks/1");

await Expect.That(response).HasStatusCode(HttpStatusCode.NotFound);

fails with the request and the response:

Failure message
Expected that response
has status code 404 NotFound,
but it had status code 200 OK

HTTP-Request:
GET http://localhost/tracks/1 HTTP/1.1

HTTP-Response:
200 OK HTTP/1.1
x-vendor: VENDOR
Content-Type: application/json; charset=utf-8
Content-Length: 51
{
"id": 1,
"title": "Let It Be",
"artist": "The Beatles"
}

Response headers​

You can verify that the HttpResponseMessage has a header, optionally with the expected value, or that it does not have it:

HttpResponseMessage response = await httpClient.GetAsync("https://music.example.com/tracks/1");

await Expect.That(response).HasHeader("Cache-Control");
await Expect.That(response).HasHeader("Cache-Control").WithValue("must-revalidate, max-age=0, private");
await Expect.That(response).DoesNotHaveHeader("Set-Cookie");

You can also add expectations on the header value or on all of its values:

HttpResponseMessage response = await httpClient.GetAsync("https://music.example.com/tracks/1");

await Expect.That(response).HasHeader("ETag")
.WhoseValue(value => value.IsNotEmpty());
await Expect.That(response).HasHeader("Vary")
.WhoseValues(values => values.Contains("Accept-Encoding"));

Content type​

You can verify the media type in the Content-Type header of the HttpResponseMessage:

HttpResponseMessage response = await httpClient.GetAsync("/tracks/1");

await Expect.That(response).HasContentType("application/json");
await Expect.That(response).HasContentType("application/*").AsWildcard();
HttpResponseMessage response = await httpClient.GetAsync("/tracks/1");

await Expect.That(response).HasContentType("text/plain");
Failure message
Expected that response
has a `Content-Type` header equal to "text/plain",
but it had content type "application/json", which differs at index 0:
↓ (actual)
"application/json"
"text/plain"
↑ (expected)

HTTP-Request:
GET http://localhost/tracks/1 HTTP/1.1

HTTP-Response:
200 OK HTTP/1.1
x-vendor: VENDOR
Content-Type: application/json; charset=utf-8
Content-Length: 51
{
"id": 1,
"title": "Let It Be",
"artist": "The Beatles"
}

Response content​

You can verify that the HttpResponseMessage has the expected string content:

HttpResponseMessage response = await httpClient.GetAsync("https://music.example.com/tracks/1");

await Expect.That(response).HasContent("*Let It Be*").AsWildcard();
await Expect.That(response).HasContent(content => content.Contains("The Beatles"));

The content supports the same string options and match types as comparing strings. With aweXpect.Json, the nested expectations can also verify JSON content:

HttpResponseMessage response = await httpClient.GetAsync("https://music.example.com/tracks/1");

await Expect.That(response).HasContent(content => content
.IsValidJsonMatching(new { title = "Let It Be", artist = "The Beatles" }));

Problem details​

You can verify that the content of the HttpResponseMessage is a valid ProblemDetails object, optionally with the expected type, title, status, detail and instance:

HttpResponseMessage response = await httpClient.GetAsync("https://music.example.com/tracks/42");

await Expect.That(response)
.HasProblemDetailsContent("https://httpstatuses.com/404")
.WithTitle("Track not found")
.WithStatus(404)
.WithInstance("/tracks/42");

The type, title, detail and instance support the same string options and match types as comparing strings.

Request message​

You can add expectations on the HttpRequestMessage that led to the HttpResponseMessage:

HttpResponseMessage response = await httpClient.GetAsync("https://music.example.com/tracks/1");

await Expect.That(response).HasRequestMessage(request => request
.HasMethod(HttpMethod.Get).And
.HasRequestUri("https://music.example.com/tracks/1"));

Uri​

Kind​

You can verify what kind of URI the Uri is, or that it is not:

Uri uri = new("https://music.example.com/tracks/1");

await Expect.That(uri).IsAbsolute();
await Expect.That(uri).IsNotFile();
await Expect.That(uri).IsNotLoopback();
await Expect.That(uri).IsNotUnc();
Uri uri = new("/tracks/1", UriKind.Relative);

await Expect.That(uri).IsAbsolute();
Failure message
Expected that uri
is an absolute URI,
but it was /tracks/1

Default port​

You can verify that the Uri uses the default port of its scheme, or that it does not:

await Expect.That(new Uri("https://music.example.com/tracks/1")).HasDefaultPort();
await Expect.That(new Uri("https://music.example.com:8443/tracks/1")).DoesNotHaveDefaultPort();

Scheme​

You can verify the scheme of the Uri:

Uri uri = new("https://music.example.com/tracks/1");

await Expect.That(uri).HasScheme().EqualTo("https");
await Expect.That(uri).HasScheme().NotEqualTo("http");

Customization​

The failure messages include the content of the request and the response. Each content is formatted by the first content processor that can handle it: by default JSON is indented, text is shown as is, and audio, image, video and PDF content is summarized with its media type and length. You can change the processors, e.g. with your own IContentProcessor. The value is restored when the returned lifetime is disposed:

using aweXpect.Customization;
using aweXpect.Web;
using aweXpect.Web.ContentProcessors;

using (Customize.aweXpect.Web().ContentProcessors.Set([
new MyCsvContentProcessor(),
new JsonContentProcessor(),
new StringContentProcessor(),
new BinaryContentProcessor(),
]))
{
// formats CSV content with the custom processor
}

To change the processors for all tests, set them on Customize.aweXpect.Global.Web() instead.