HttpMessageHandlerExtensions: NSubstitute.AutoMock. HttpResponseExtensions and HttpMessageHandlerWrapper: NSubstitute.AutoMock.Http. Documentation index.
AutoMocker resolves HttpClient with a partial HttpMessageHandlerWrapper substitute. Unconfigured requests return HTTP 200 with empty content, regardless of AutoMocker.CallBase.
using System.Net;
using System.Net.Http;
using NSubstitute;
using NSubstitute.AutoMock;
using NSubstitute.AutoMock.Http;
var mocker = new AutoMocker();
mocker.SetupHttpGet("/users")
.ReturnsHttpResponse(HttpStatusCode.OK, "[]", mediaType: "application/json");
using var response = await mocker.Get<HttpClient>()
.GetAsync("https://example.test/users");
mocker.VerifyHttpGet("/users", requiredNumberOfCalls: 1);Each method below has overloads on both AutoMocker and HttpMessageHandlerWrapper. All return Task<HttpResponseMessage>, the pending substitute call; immediately chain ReturnsHttpResponse, ReturnsResponse, or NSubstitute's Returns.
Receivers are omitted:
| Method | Overloads |
|---|---|
SetupHttpGet |
(string? requestUri = null); (Func<HttpRequestMessage, bool> match) |
SetupHttpPost |
(string? requestUri = null, string? content = null); (Func<HttpRequestMessage, bool> match) |
SetupHttpPut |
(string? requestUri = null, string? content = null); (Func<HttpRequestMessage, bool> match) |
SetupHttpPatch |
(string? requestUri = null, string? content = null); (Func<HttpRequestMessage, bool> match) |
SetupHttpDelete |
(string? requestUri = null); (Func<HttpRequestMessage, bool> match) |
SetupHttpHead |
(string? requestUri = null); (Func<HttpRequestMessage, bool> match) |
SetupHttp |
(Func<HttpRequestMessage, bool> match) |
Verb helpers restrict the HTTP method. SetupHttp matches any method satisfying the predicate. URI matching is a case-sensitive substring of the absolute URI; body matching is exact text. Null URI/content filters match anything.
mocker.SetupHttpPost("/orders", "order data")
.ReturnsHttpResponse(HttpStatusCode.Created);
mocker.SetupHttpPut("/orders/1", "replacement")
.ReturnsHttpResponse(HttpStatusCode.OK);
mocker.SetupHttpPatch("/orders/1", "change")
.ReturnsHttpResponse(HttpStatusCode.OK);
mocker.SetupHttpDelete("/orders/1")
.ReturnsHttpResponse(HttpStatusCode.NoContent);
mocker.SetupHttpHead("/health")
.ReturnsHttpResponse(HttpStatusCode.OK);
var handler = mocker.GetSubstitute<HttpMessageHandlerWrapper>();
handler.SetupHttpGet(r => r.RequestUri!.AbsolutePath == "/users")
.ReturnsHttpResponse("[]");
handler.SetupHttp(r => r.Method == HttpMethod.Options)
.ReturnsHttpResponse(HttpStatusCode.NoContent);All overloads extend Task<HttpResponseMessage> and return NSubstitute.Core.ConfiguredCall. Receivers and return types are omitted below. Every configure callback can change headers or other response properties.
ReturnsHttpResponse(
HttpStatusCode statusCode, Action<HttpResponseMessage>? configure = null)
ReturnsHttpResponse(
HttpStatusCode statusCode, HttpContent content,
Action<HttpResponseMessage>? configure = null)
ReturnsHttpResponse(
HttpStatusCode statusCode, string content,
string? mediaType = null, Encoding? encoding = null,
Action<HttpResponseMessage>? configure = null)
ReturnsHttpResponse(
string content, string? mediaType = null, Encoding? encoding = null,
Action<HttpResponseMessage>? configure = null)The status-free overload uses HTTP 200. String content defaults to UTF-8 and text/plain.
mocker.SetupHttpGet("/users")
.ReturnsHttpResponse(HttpStatusCode.OK, "[]", "application/json",
configure: response => response.Headers.Add("X-Test", "true"));
mocker.SetupHttpGet("/custom")
.ReturnsHttpResponse(HttpStatusCode.OK, new StringContent("custom"));ReturnsResponse(
HttpStatusCode statusCode, byte[] content, string? mediaType = null,
Action<HttpResponseMessage>? configure = null)
ReturnsResponse(
byte[] content, string? mediaType = null,
Action<HttpResponseMessage>? configure = null)
ReturnsResponse(
HttpStatusCode statusCode, Stream content, string? mediaType = null,
Action<HttpResponseMessage>? configure = null)
ReturnsResponse(
Stream content, string? mediaType = null,
Action<HttpResponseMessage>? configure = null)Status-free overloads use HTTP 200. Seekable streams are reset to position zero at setup time. Builders reuse a configured response/content object; use NSubstitute Returns with a factory when each call needs a fresh response. Non-null content is required by every content overload.
mocker.SetupHttpGet("/bytes")
.ReturnsResponse(new byte[] { 1, 2, 3 }, "application/octet-stream");
mocker.SetupHttpGet("/stream")
.ReturnsResponse(HttpStatusCode.OK, new MemoryStream(new byte[] { 4, 5 }));
mocker.SetupHttpGet("/fresh").Returns(_ =>
Task.FromResult(new HttpResponseMessage(HttpStatusCode.OK)
{
Content = new StringContent("fresh per call")
}));Verb-specific methods extend AutoMocker only:
void VerifyHttpGet(string? requestUri = null, int? requiredNumberOfCalls = null)
void VerifyHttpPost(
string? requestUri = null, string? content = null, int? requiredNumberOfCalls = null)
void VerifyHttpPut(
string? requestUri = null, string? content = null, int? requiredNumberOfCalls = null)
void VerifyHttpPatch(
string? requestUri = null, string? content = null, int? requiredNumberOfCalls = null)
void VerifyHttpDelete(string? requestUri = null, int? requiredNumberOfCalls = null)
void VerifyHttpHead(string? requestUri = null, int? requiredNumberOfCalls = null)The lower-level helper extends HttpMessageHandlerWrapper only:
void VerifyHttp(
HttpMethod method, Func<HttpRequestMessage, bool> match,
int? requiredNumberOfCalls = null)Matching follows setup rules. A null count means at least once; a supplied count is exact, including zero for no calls. Failed checks throw NSubstitute's received-call exception.
mocker.VerifyHttpPost("/orders", "order data", requiredNumberOfCalls: 1);
mocker.VerifyHttpPut("/orders/1", "replacement", requiredNumberOfCalls: 1);
mocker.VerifyHttpPatch("/orders/1", "change", requiredNumberOfCalls: 1);
mocker.VerifyHttpDelete("/orders/1", requiredNumberOfCalls: 0);
mocker.VerifyHttpHead("/health", requiredNumberOfCalls: 1);
handler.VerifyHttp(HttpMethod.Get,
r => r.RequestUri!.AbsolutePath == "/users", requiredNumberOfCalls: 1);HttpMessageHandlerWrapper is an abstract HttpMessageHandler with a protected parameterless constructor. Its protected SendAsync override delegates to:
public abstract Task<HttpResponseMessage> SendAsyncPublic(
HttpRequestMessage request, CancellationToken cancellationToken);HttpMessageHandlerExtensions.CreateClient(this HttpMessageHandlerWrapper handler) returns an HttpClient without transferring handler ownership. Dispose the client separately.
var handler = mocker.GetSubstitute<HttpMessageHandlerWrapper>();
using HttpClient client = handler.CreateClient();
await handler.Received(1).SendAsyncPublic(
Arg.Is<HttpRequestMessage>(r => r.Method == HttpMethod.Get),
Arg.Any<CancellationToken>());For named clients, see WithHttpClientFactory. The generated factory caches one client per name; named clients share the testable handler.