Extends Verify to allow verification of Http bits.
https://nuget.org/packages/Verify.Http/
Enable VerifyHttp once at assembly load time:
VerifyHttp.Enable();
Includes converters for the following
HttpMethod
Uri
HttpHeaders
HttpContent
HttpRequestMessage
HttpResponseMessage
For example:
[Fact]
public async Task HttpResponse()
{
using var client = new HttpClient();
var result = await client.GetAsync("https://httpbin.org/get");
await Verify(result);
}
Results in:
{
Version: 1.1,
Status: 200 OK,
Headers: {
Access-Control-Allow-Credentials: true,
Connection: keep-alive,
Date: DateTime_1,
Server: gunicorn/19.9.0
},
Content: {
Headers: {
Content-Type: application/json
},
Value: {
args: {},
headers: {
Host: httpbin.org,
},
url: https://httpbin.org/get
}
},
Request: https://httpbin.org/get
}
For code that does web calls via HttpClient, these calls can be recorded and verified.
Given a class that does some Http calls:
public class MyService
{
HttpClient client;
// Resolve a HttpClient. All http calls done at any
// resolved client will be added to `recording.Sends`
public MyService(HttpClient client) =>
this.client = client;
public Task MethodThatDoesHttp() =>
// Some code that does some http calls
client.GetAsync("https://httpbin.org/status/undefined");
}
Http recording can be added to a IHttpClientBuilder
:
var collection = new ServiceCollection();
collection.AddScoped<MyService>();
var httpBuilder = collection.AddHttpClient<MyService>();
// Adds a AddHttpClient and adds a RecordingHandler using AddHttpMessageHandler
var recording = httpBuilder.AddRecording();
await using var provider = collection.BuildServiceProvider();
var myService = provider.GetRequiredService<MyService>();
await myService.MethodThatDoesHttp();
await Verify(recording.Sends)
// Ignore some headers that change per request
.ModifySerialization(x => x.IgnoreMembers("Date"));
Http can also be added globally IHttpClientBuilder
:
Note: This only seems to work in net5 and up.
var collection = new ServiceCollection();
collection.AddScoped<MyService>();
// Adds a AddHttpClient and adds a RecordingHandler using AddHttpMessageHandler
var (builder, recording) = collection.AddRecordingHttpClient();
await using var provider = collection.BuildServiceProvider();
var myService = provider.GetRequiredService<MyService>();
await myService.MethodThatDoesHttp();
await Verify(recording.Sends)
// Ignore some headers that change per request
.ModifySerialization(x => x.IgnoreMembers("Date"));
Will result in the following verified file:
[
{
RequestUri: https://httpbin.org/status/undefined,
RequestMethod: GET,
ResponseStatus: BadRequest,
ResponseHeaders: {
Access-Control-Allow-Credentials: true,
Connection: keep-alive,
Server: gunicorn/19.9.0
},
ResponseContent: Invalid status code
}
]
There a Pause/Resume semantics:
var collection = new ServiceCollection();
collection.AddScoped<MyService>();
var httpBuilder = collection.AddHttpClient<MyService>();
// Adds a AddHttpClient and adds a RecordingHandler using AddHttpMessageHandler
var recording = httpBuilder.AddRecording();
await using var provider = collection.BuildServiceProvider();
var myService = provider.GetRequiredService<MyService>();
// Recording is enabled by default. So Pause to stop recording
recording.Pause();
await myService.MethodThatDoesHttp();
// Resume recording
recording.Resume();
await myService.MethodThatDoesHttp();
await Verify(recording.Sends)
.ModifySerialization(x => x.IgnoreMembers("Date"));
If the AddRecordingHttpClient
helper method does not meet requirements, the RecordingHandler
can be explicitly added:
var collection = new ServiceCollection();
var builder = collection.AddHttpClient("name");
// Change to not recording at startup
var recording = new RecordingHandler(recording: false);
builder.AddHttpMessageHandler(() => recording);
await using var provider = collection.BuildServiceProvider();
var factory = provider.GetRequiredService<IHttpClientFactory>();
var client = factory.CreateClient("name");
await client.GetAsync("https://www.google.com/");
recording.Resume();
await client.GetAsync("https://httpbin.org/status/undefined");
await Verify(recording.Sends)
.ModifySerialization(x => x.IgnoreMembers("Date"));
Http Recording allows, when a method is being tested, for any http requests made as part of that method call to be recorded and verified.
Supported in net5 and up
Call HttpRecording.StartRecording();
before the method being tested is called.
The perform the verification as usual:
[Fact]
public async Task TestHttpRecording()
{
HttpRecording.StartRecording();
var sizeOfResponse = await MethodThatDoesHttpCalls();
await Verify(
new
{
sizeOfResponse
})
.ModifySerialization(settings =>
{
//scrub some headers that are not consistent between test runs
settings.IgnoreMembers("traceparent", "Date");
});
}
static async Task<int> MethodThatDoesHttpCalls()
{
using var client = new HttpClient();
var jsonResult = await client.GetStringAsync("https://httpbin.org/json");
var xmlResult = await client.GetStringAsync("https://httpbin.org/xml");
return jsonResult.Length + xmlResult.Length;
}
The requests/response pairs will be appended to the verified file.
{
target: {
sizeOfResponse: 951
},
httpCalls: [
{
Request: {
Uri: https://httpbin.org/json,
Headers: {}
},
Response: {
Status: 200 OK,
Headers: {
Access-Control-Allow-Credentials: true,
Connection: keep-alive,
Server: gunicorn/19.9.0
},
ContentHeaders: {
Content-Type: application/json
},
ContentStringParsed: {
slideshow: {
author: Yours Truly,
date: date of publication,
slides: [
{
title: Wake up to WonderWidgets!,
type: all
},
{
items: [
Why <em>WonderWidgets</em> are great,
Who <em>buys</em> WonderWidgets
],
title: Overview,
type: all
}
],
title: Sample Slide Show
}
}
}
},
{
Request: {
Uri: https://httpbin.org/xml,
Headers: {}
},
Response: {
Status: 200 OK,
Headers: {
Access-Control-Allow-Credentials: true,
Connection: keep-alive,
Server: gunicorn/19.9.0
},
ContentHeaders: {
Content-Type: application/xml
},
ContentStringParsed: {
?xml: {
@version: 1.0,
@encoding: us-ascii
}/* A SAMPLE set of slides */,
slideshow: {
@title: Sample Slide Show,
@date: Date of publication,
@author: Yours Truly,
#comment: [],
slide: [
{
@type: all,
title: Wake up to WonderWidgets!
},
{
@type: all,
title: Overview,
item: [
{
#text: [
Why ,
are great
],
em: WonderWidgets
},
null,
{
#text: [
Who ,
WonderWidgets
],
em: buys
}
]
}
]
}
}
}
}
]
}
The above usage results in the http calls being automatically added snapshot file. Calls can also be explicitly read and recorded using HttpRecording.FinishRecording()
. This enables:
- Filtering what http calls are included in the snapshot.
- Only verifying a subset of information for each http call.
- Performing additional asserts on http calls.
For example:
[Fact]
public async Task TestHttpRecordingExplicit()
{
HttpRecording.StartRecording();
var sizeOfResponse = await MethodThatDoesHttpCalls();
var httpCalls = HttpRecording.FinishRecording().ToList();
// Ensure all calls finished in under 5 seconds
var threshold = TimeSpan.FromSeconds(5);
foreach (var call in httpCalls)
{
Assert.True(call.Duration < threshold);
}
await Verify(
new
{
sizeOfResponse,
// Only use the Uri in the snapshot
httpCalls = httpCalls.Select(_ => _.Request.Uri)
});
}
Results in the following:
{
sizeOfResponse: 951,
httpCalls: [
https://httpbin.org/json,
https://httpbin.org/xml
]
}
Spider designed by marialuisa iborra from The Noun Project.