Skip to content

Extends Verify to allow verification of web bits.

License

Notifications You must be signed in to change notification settings

egil/Verify.Http

 
 

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Verify.Http

Build status NuGet Status

Extends Verify to allow verification of Http bits.

NuGet package

https://nuget.org/packages/Verify.Http/

Enable

Enable VerifyHttp once at assembly load time:

VerifyHttp.Enable();

snippet source | anchor

Converters

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);
}

snippet source | anchor

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
}

snippet source | anchor

HttpClient recording via Service

For code that does web calls via HttpClient, these calls can be recorded and verified.

Service that does http

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");
}

snippet source | anchor

Add to IHttpClientBuilder

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"));

snippet source | anchor

Add globally

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"));

snippet source | anchor

Result

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
  }
]

snippet source | anchor

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"));

snippet source | anchor

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"));

snippet source | anchor

Http Recording via listener

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

Usage

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;
}

snippet source | anchor

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
                  }
                ]
              }
            ]
          }
        }
      }
    }
  ]
}

snippet source | anchor

Explicit Usage

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)
        });
}

snippet source | anchor

Results in the following:

{
  sizeOfResponse: 951,
  httpCalls: [
    https://httpbin.org/json,
    https://httpbin.org/xml
  ]
}

snippet source | anchor

Icon

Spider designed by marialuisa iborra from The Noun Project.

About

Extends Verify to allow verification of web bits.

Resources

License

Code of conduct

Stars

Watchers

Forks

Packages

No packages published

Languages

  • C# 100.0%