JustEat.HttpClientInterception
4.1.0
Prefix Reserved
See the version list below for details.
dotnet add package JustEat.HttpClientInterception --version 4.1.0
NuGet\Install-Package JustEat.HttpClientInterception -Version 4.1.0
<PackageReference Include="JustEat.HttpClientInterception" Version="4.1.0" />
paket add JustEat.HttpClientInterception --version 4.1.0
#r "nuget: JustEat.HttpClientInterception, 4.1.0"
// Install JustEat.HttpClientInterception as a Cake Addin #addin nuget:?package=JustEat.HttpClientInterception&version=4.1.0 // Install JustEat.HttpClientInterception as a Cake Tool #tool nuget:?package=JustEat.HttpClientInterception&version=4.1.0
HttpClient Interception
A .NET Standard library for intercepting server-side HTTP dependencies.
Introduction
This library provides functionality for intercepting HTTP requests made using the HttpClient
class in code targeting .NET Standard 2.0 (and later), and .NET Framework 4.7.2.
The primary use-case is for providing stub responses for use in tests for applications, such as an ASP.NET Core application, to drive your functional test scenarios.
The library is based around an implementation of DelegatingHandler
, which can either be used directly as an implementation of HttpMessageHandler
, or can be provided to instances of HttpClient
. This also allows it to be registered via Dependency Injection to make it available for use in code under test without the application itself requiring any references to JustEat.HttpClientInterception
or any custom abstractions of HttpClient
.
This design means that no HTTP server needs to be hosted to proxy traffic to/from, so does not consume any additional system resources, such as needing to bind a port for HTTP traffic, making it lightweight to use.
Installation
To install the library from NuGet using the .NET SDK run:
dotnet add package JustEat.HttpClientInterception
Basic Examples
Request Interception
Fluent API
Below is a minimal example of intercepting an HTTP GET request to an API for a JSON resource to return a custom response using the fluent API:
<a id='snippet-minimal-example'></a>
// Arrange
var options = new HttpClientInterceptorOptions();
var builder = new HttpRequestInterceptionBuilder();
builder
.Requests()
.ForGet()
.ForHttps()
.ForHost("public.je-apis.com")
.ForPath("terms")
.Responds()
.WithJsonContent(new { Id = 1, Link = "https://www.just-eat.co.uk/privacy-policy" })
.RegisterWith(options);
using var client = options.CreateHttpClient();
// Act
// The value of json will be: {"Id":1, "Link":"https://www.just-eat.co.uk/privacy-policy"}
string json = await client.GetStringAsync("https://public.je-apis.com/terms");
<sup><a href='/tests/HttpClientInterception.Tests/Examples.cs#L44-L66' title='Snippet source file'>snippet source</a> | <a href='#snippet-minimal-example' title='Start of snippet'>anchor</a></sup>
HttpRequestInterceptionBuilder
objects are mutable, so properties can be freely changed once a particular setup has been registered with an instance of HttpClientInterceptorOptions
as the state is captured at the point of registration. This allows multiple responses and paths to be configured from a single HttpRequestInterceptionBuilder
instance where multiple registrations against a common hostname.
HTTP Bundle Files
HTTP requests to intercept can also be configured in an "HTTP bundle" file, which can be used to store the HTTP requests to intercept and their corresponding responses as JSON.
This functionality is analogous to our Shock pod for iOS.
JSON
Below is an example bundle file, which can return content in formats such as a string, JSON and base64-encoded data.
The full JSON schema for HTTP bundle files can be found here.
<a id='snippet-sample-bundle.json'></a>
{
"$schema": "https://raw.githubusercontent.com/justeattakeaway/httpclient-interception/main/src/HttpClientInterception/Bundles/http-request-bundle-schema.json",
"id": "my-bundle",
"comment": "A bundle of HTTP requests",
"items": [
{
"id": "home",
"comment": "Returns the home page",
"uri": "https://www.just-eat.co.uk",
"contentString": "<html><head><title>Just Eat</title></head></html>"
},
{
"id": "terms",
"comment": "Returns the Ts & Cs",
"uri": "https://public.je-apis.com/terms",
"contentFormat": "json",
"contentJson": {
"Id": 1,
"Link": "https://www.just-eat.co.uk/privacy-policy"
}
}
]
}
<sup><a href='/tests/HttpClientInterception.Tests/Bundles/sample-bundle.json#L1-L23' title='Snippet source file'>snippet source</a> | <a href='#snippet-sample-bundle.json' title='Start of snippet'>anchor</a></sup>
Code
// using JustEat.HttpClientInterception;
var options = new HttpClientInterceptorOptions().RegisterBundle("my-bundle.json");
var client = options.CreateHttpClient();
// The value of html will be "<html><head><title>Just Eat</title></head></html>"
var html = await client.GetStringAsync("https://www.just-eat.co.uk");
// The value of json will be "{\"Id\":1,\"Link\":\"https://www.just-eat.co.uk/privacy-policy\"}"
var json = await client.GetStringAsync("https://public.je-apis.com/terms");
Further examples of using HTTP bundles can be found in the tests, such as for changing the response code, the HTTP method, and matching to HTTP requests based on the request headers.
Fault Injection
Below is a minimal example of intercepting a request to inject an HTTP fault:
<a id='snippet-fault-injection'></a>
var options = new HttpClientInterceptorOptions();
var builder = new HttpRequestInterceptionBuilder()
.Requests()
.ForHost("public.je-apis.com")
.WithStatus(HttpStatusCode.InternalServerError)
.RegisterWith(options);
var client = options.CreateHttpClient();
// Throws an HttpRequestException
await Assert.ThrowsAsync<HttpRequestException>(
() => client.GetStringAsync("http://public.je-apis.com"));
<sup><a href='/tests/HttpClientInterception.Tests/Examples.cs#L23-L38' title='Snippet source file'>snippet source</a> | <a href='#snippet-fault-injection' title='Start of snippet'>anchor</a></sup>
Registering Request Interception When Using IHttpClientFactory
If you are using IHttpClientFactory
to register HttpClient
for Dependency Injection in a .NET Core 3.1 application (or later), you can implement a custom IHttpMessageHandlerBuilderFilter
to register during test setup, which makes an instance of HttpClientInterceptorOptions
available to inject an HTTP handler.
A working example of this approach can be found in the sample application.
<a id='snippet-interception-filter'></a>
/// <summary>
/// A class that registers an intercepting HTTP message handler at the end of
/// the message handler pipeline when an <see cref="HttpClient"/> is created.
/// </summary>
public sealed class HttpClientInterceptionFilter(HttpClientInterceptorOptions options) : IHttpMessageHandlerBuilderFilter
{
/// <inheritdoc/>
public Action<HttpMessageHandlerBuilder> Configure(Action<HttpMessageHandlerBuilder> next)
{
return (builder) =>
{
// Run any actions the application has configured for itself
next(builder);
// Add the interceptor as the last message handler
builder.AdditionalHandlers.Add(options.CreateHttpMessageHandler());
};
}
}
<sup><a href='/samples/SampleApp.Tests/HttpClientInterceptionFilter.cs#L9-L31' title='Snippet source file'>snippet source</a> | <a href='#snippet-interception-filter' title='Start of snippet'>anchor</a></sup>
Setting Up HttpClient for Dependency Injection Manually
Below is an example of setting up IServiceCollection
to register HttpClient
for Dependency Injection in a manner that allows tests to use HttpClientInterceptorOptions
to intercept HTTP requests. Similar approaches can be used with other IoC containers.
You may wish to consider registering HttpClient
as a singleton, rather than as transient, if you do not use properties such as BaseAddress
on instances of HttpClient
. This allows the same instance to be used throughout the application, which improves performance and resource utilisation under heavy server load. If using a singleton instance, ensure that you manage the lifetime of your message handlers appropriately so they are not disposed of incorrectly and update the registration for your HttpClient
instance appropriately.
services.AddTransient(
(serviceProvider) =>
{
// Create a handler that makes actual HTTP calls
HttpMessageHandler handler = new HttpClientHandler();
// Have any delegating handlers been registered?
var handlers = serviceProvider
.GetServices<DelegatingHandler>()
.ToList();
if (handlers.Count > 0)
{
// Attach the initial handler to the first delegating handler
DelegatingHandler previous = handlers.First();
previous.InnerHandler = handler;
// Chain any remaining handlers to each other
foreach (DelegatingHandler next in handlers.Skip(1))
{
next.InnerHandler = previous;
previous = next;
}
// Replace the initial handler with the last delegating handler
handler = previous;
}
// Create the HttpClient using the inner HttpMessageHandler
return new HttpClient(handler);
});
Then in the test project register HttpClientInterceptorOptions
to provide an implementation of DelegatingHandler
. If using a singleton for HttpClient
as described above, update the registration for the tests appropriately so that the message handler is shared.
var options = new HttpClientInterceptorOptions();
var server = new WebHostBuilder()
.UseStartup<Startup>()
.ConfigureServices(
(services) => services.AddTransient((_) => options.CreateHttpMessageHandler()))
.Build();
server.Start();
Further Examples
Further examples of using the library can be found by following the links below:
- Example tests
- Sample application with tests
- This library's own tests
Benchmarks
Generated with the Benchmarks project using BenchmarkDotNet using commit c31abf3 on 15/11/2022.
BenchmarkDotNet=v0.13.2, OS=Windows 11 (10.0.22000.1098/21H2)
Intel Core i9-9980HK CPU 2.40GHz, 1 CPU, 16 logical and 8 physical cores
.NET SDK=7.0.100
[Host] : .NET 7.0.0 (7.0.22.51805), X64 RyuJIT AVX2
DefaultJob : .NET 7.0.0 (7.0.22.51805), X64 RyuJIT AVX2
Method | Mean | Error | StdDev | Gen0 | Allocated |
---|---|---|---|---|---|
GetBytes |
3.102 μs | 0.0780 μs | 0.2264 μs | 0.3128 | 2648 B |
GetHtml |
3.675 μs | 0.0735 μs | 0.2143 μs | 0.3700 | 3104 B |
GetJson |
5.175 μs | 0.1223 μs | 0.3470 μs | 0.3433 | 2904 B |
GetStream |
36.359 μs | 0.7168 μs | 1.3981 μs | 0.3662 | 3312 B |
Feedback
Any feedback or issues can be added to the issues for this project in GitHub.
Repository
The repository is hosted in GitHub: https://github.com/justeattakeaway/httpclient-interception.git
Building and Testing
Compiling the library yourself requires Git and the .NET SDK to be installed (version 7.0.100 or later).
To build and test the library locally from a terminal/command-line, run one of the following set of commands:
git clone https://github.com/justeattakeaway/httpclient-interception.git
cd httpclient-interception
./build.ps1
License
This project is licensed under the Apache 2.0 license.
Product | Versions Compatible and additional computed target framework versions. |
---|---|
.NET | net5.0 was computed. net5.0-windows was computed. net6.0 is compatible. net6.0-android was computed. net6.0-ios was computed. net6.0-maccatalyst was computed. net6.0-macos was computed. net6.0-tvos was computed. net6.0-windows was computed. net7.0 is compatible. net7.0-android was computed. net7.0-ios was computed. net7.0-maccatalyst was computed. net7.0-macos was computed. net7.0-tvos was computed. net7.0-windows was computed. net8.0 was computed. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. |
.NET Core | netcoreapp2.0 was computed. netcoreapp2.1 was computed. netcoreapp2.2 was computed. netcoreapp3.0 was computed. netcoreapp3.1 was computed. |
.NET Standard | netstandard2.0 is compatible. netstandard2.1 was computed. |
.NET Framework | net461 was computed. net462 was computed. net463 was computed. net47 was computed. net471 was computed. net472 is compatible. net48 was computed. net481 was computed. |
MonoAndroid | monoandroid was computed. |
MonoMac | monomac was computed. |
MonoTouch | monotouch was computed. |
Tizen | tizen40 was computed. tizen60 was computed. |
Xamarin.iOS | xamarinios was computed. |
Xamarin.Mac | xamarinmac was computed. |
Xamarin.TVOS | xamarintvos was computed. |
Xamarin.WatchOS | xamarinwatchos was computed. |
-
.NETFramework 4.7.2
- Microsoft.AspNetCore.WebUtilities (>= 2.2.0)
- System.Net.Http (>= 4.3.4)
- System.Text.Json (>= 4.7.2)
-
.NETStandard 2.0
- Microsoft.AspNetCore.WebUtilities (>= 2.2.0)
- System.Text.Json (>= 4.7.2)
-
net6.0
- Microsoft.AspNetCore.WebUtilities (>= 2.2.0)
- System.Text.Json (>= 6.0.0)
-
net7.0
- Microsoft.AspNetCore.WebUtilities (>= 2.2.0)
- System.Text.Json (>= 4.7.2)
NuGet packages (4)
Showing the top 4 NuGet packages that depend on JustEat.HttpClientInterception:
Package | Downloads |
---|---|
Fun.AspNetCore.Mvc.Testing.HttpClientInterception
Extensions and utility code for testing ASP.NET Core web applications with HttpClient interception |
|
BreakPoint.Util.SCCommunication
Logs communications |
|
RezisFramework
Package Description |
|
MockTracer
Base test class for generated code |
GitHub repositories (4)
Showing the top 4 popular GitHub repositories that depend on JustEat.HttpClientInterception:
Repository | Stars |
---|---|
aspnet-contrib/AspNet.Security.OAuth.Providers
OAuth 2.0 social authentication providers for ASP.NET Core
|
|
martincostello/dotnet-minimal-api-integration-testing
An example of integration testing ASP.NET Core Minimal hosting and APIs
|
|
PlexRipper/PlexRipper
The best cross-platform Plex media downloader there is! In active development and feedback is very welcome!
|
|
josephwoodward/Serilog-Sinks-Loki
A Serilog Sink for Loki, Grafana's new Prometheus inspired log aggregator
|
Version | Downloads | Last updated |
---|---|---|
4.3.0 | 150,440 | 4/13/2024 |
4.2.1 | 19,300 | 3/14/2024 |
4.2.0 | 22,524 | 2/6/2024 |
4.1.0 | 55,316 | 12/11/2023 |
4.0.0 | 365,286 | 11/15/2022 |
3.2.0 | 11,943 | 11/15/2022 |
3.1.2 | 100,238 | 8/1/2022 |
3.1.1 | 226,236 | 8/3/2021 |
3.1.0 | 150,386 | 11/10/2020 |
3.0.0 | 125,085 | 10/11/2019 |
2.0.2 | 21,615 | 8/2/2019 |
2.0.1 | 20,401 | 4/28/2019 |
2.0.0 | 11,612 | 3/25/2019 |
2.0.0-beta1 | 1,333 | 3/17/2019 |
1.2.2 | 29,068 | 10/14/2018 |
1.2.1 | 19,802 | 3/11/2018 |
1.2.0 | 2,125 | 3/7/2018 |
1.1.0 | 2,887 | 2/23/2018 |
1.0.0 | 4,732 | 10/2/2017 |
See https://github.com/justeattakeaway/httpclient-interception/releases for details.