Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,15 @@ All notable changes to TestableHttpClient will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and
this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [0.9] - unplanned
## [0.9] - 2022-11-25
### Deprecated
- `Responses.NoContent()` has been deprecated, since it doesn't fit well with the rest of the API. Please use `Responses.StatusCode(HttpStatusCode.NoContent)` instead.

### Removed
- Official support for .NET Core 3.1 has been removed. This means we no longer provide a specific version for .NET Core 3.0 and we no longer test this version explicitly. Since we support .NET Standard 2.0, the library could still be used.
- TestableHttpClient.NFluent has been moved to it's own repository.
- `HttpRequestMessageExtensions` have been made internal.
- `HttpResponseMessageExtensions` have been removed, since it not needed for making HttpClients testable.

### Added
- Added `Responses.Route` that allows changing the response based on the url. The url supports patterns.
Expand Down Expand Up @@ -243,6 +245,7 @@ this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
- Automatically build project when pushing changes to github and when creating a pull request
- Automatically deploy to NuGet when creating a tag in github

[0.9]: https://github.com/testablehttpclient/TestableHttpClient/compare/v0.8...v0.9
[0.8]: https://github.com/testablehttpclient/TestableHttpClient/compare/v0.7...v0.8
[0.7]: https://github.com/testablehttpclient/TestableHttpClient/compare/v0.6...v0.7
[0.6]: https://github.com/testablehttpclient/TestableHttpClient/compare/v0.5...v0.6
Expand Down
20 changes: 16 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# TestableHttpClient

![GitHub](https://img.shields.io/github/license/testablehttpclient/TestableHttpClient) ![GitHub Workflow Status](https://img.shields.io/github/workflow/status/testablehttpclient/TestableHttpClient/CI)
![GitHub](https://img.shields.io/github/license/testablehttpclient/TestableHttpClient) ![GitHub Workflow Status](https://img.shields.io/github/workflow/status/testablehttpclient/TestableHttpClient/CI) ![Nuget](https://img.shields.io/nuget/v/TestableHttpClient)

Creating unittest for code that uses `HttpClient` can be difficult to test. It requires a custom HttpMessageHandler or a mocked version. TestableHttpClient provides a testable version of HttpMessageHandler and several helper functions to configure the `TestableHttpHandler` and several ways to assert which requests were made.

Expand All @@ -13,9 +13,21 @@ dotnet add package TestableHttpClient

## How to use TestableHttpClient

The following code block shows the basic use case for asserting that certain requests are made:
```csharp
var testHandler = new TestableHttpMessageHandler();
var httpClient = new HttpClient(testHandler); // or testHandler.CreateClient();
TestableHttpMessageHandler testHandler = new();
HttpClient httpClient = new(testHandler); // or testHandler.CreateClient();

var result = await httpClient.GetAsync("http://httpbin.org/status/200");

testHandler.ShouldHaveMadeRequestsTo("https://httpbin.org/*");
```

The default response is an empty response message with a 200 OK StatusCode, in order to return real content the response need to be configured:
```csharp
TestableHttpMessageHandler testHandler = new();
testHandler.RespondWith(Responses.Json(new { Hello: "World" }));
HttpClient httpClient = new(testHandler); // or testHandler.CreateClient();

var result = await httpClient.GetAsync("http://httpbin.org/status/200");

Expand All @@ -29,7 +41,7 @@ More examples can be found in the [IntegrationTests project](test/TestableHttpCl
TestableHttpClient is build as a netstandard2.0 library, so theoretically it can work on every .NET version that support netstandard2.0.
The following versions are being actively tested and thus supported:

- .NET Framework 4.6 and up
- .NET Framework 4.6, 4.7 and 4.8
- .NET 6.0
- .NET 7.0

Expand Down
39 changes: 13 additions & 26 deletions src/TestableHttpClient/HttpRequestMessageExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,15 @@
/// <summary>
/// A set of static methods for checking values on a <see cref="HttpRequestMessage"/>.
/// </summary>
[Obsolete("This class is not intended for public use and will be marked internal in the next release.")]
public static class HttpRequestMessageExtensions
internal static class HttpRequestMessageExtensions
{
/// <summary>
/// Determines whether a specific HttpVersion is set on a request.
/// </summary>
/// <param name="httpRequestMessage">A <see cref="HttpRequestMessage"/> to check the correct version on.</param>
/// <param name="httpVersion">The expected version.</param>
/// <returns>true when the HttpVersion matches; otherwise, false.</returns>
[Obsolete("This method is not intended for public use and will be marked internal in the next release.")]
public static bool HasHttpVersion(this HttpRequestMessage httpRequestMessage, Version httpVersion)
internal static bool HasHttpVersion(this HttpRequestMessage httpRequestMessage, Version httpVersion)
{
if (httpRequestMessage == null)
{
Expand All @@ -34,8 +32,7 @@ public static bool HasHttpVersion(this HttpRequestMessage httpRequestMessage, Ve
/// <param name="httpRequestMessage">A <see cref="HttpRequestMessage"/> to check the correct version on.</param>
/// <param name="httpVersion">The expected version.</param>
/// <returns>true when the HttpVersion matches; otherwise, false.</returns>
[Obsolete("This method is not intended for public use and will be marked internal in the next release.")]
public static bool HasHttpVersion(this HttpRequestMessage httpRequestMessage, string httpVersion)
internal static bool HasHttpVersion(this HttpRequestMessage httpRequestMessage, string httpVersion)
{
if (httpRequestMessage == null)
{
Expand All @@ -56,8 +53,7 @@ public static bool HasHttpVersion(this HttpRequestMessage httpRequestMessage, st
/// <param name="httpRequestMessage">A <see cref="HttpRequestMessage"/> to check the correct method on.</param>
/// <param name="httpMethod">The expected method.</param>
/// <returns>true when the HttpMethod matches; otherwise, false.</returns>
[Obsolete("This method is not intended for public use and will be marked internal in the next release.")]
public static bool HasHttpMethod(this HttpRequestMessage httpRequestMessage, HttpMethod httpMethod)
internal static bool HasHttpMethod(this HttpRequestMessage httpRequestMessage, HttpMethod httpMethod)
{
if (httpRequestMessage == null)
{
Expand All @@ -78,8 +74,7 @@ public static bool HasHttpMethod(this HttpRequestMessage httpRequestMessage, Htt
/// <param name="httpRequestMessage">A <see cref="HttpRequestMessage"/> to check the correct method on.</param>
/// <param name="httpMethod">The expected method.</param>
/// <returns>true when the HttpMethod matches; otherwise, false.</returns>
[Obsolete("This method is not intended for public use and will be marked internal in the next release.")]
public static bool HasHttpMethod(this HttpRequestMessage httpRequestMessage, string httpMethod)
internal static bool HasHttpMethod(this HttpRequestMessage httpRequestMessage, string httpMethod)
{
if (httpRequestMessage == null)
{
Expand All @@ -101,8 +96,7 @@ public static bool HasHttpMethod(this HttpRequestMessage httpRequestMessage, str
/// <param name="httpRequestMessage">A <see cref="HttpRequestMessage"/> to check the request header on.</param>
/// <param name="headerName">The name of the header to locate on the request.</param>
/// <returns>true when the request contains a header with the specified name; otherwise, false.</returns>
[Obsolete("This method is not intended for public use and will be marked internal in the next release.")]
public static bool HasRequestHeader(this HttpRequestMessage httpRequestMessage, string headerName)
internal static bool HasRequestHeader(this HttpRequestMessage httpRequestMessage, string headerName)
{
if (httpRequestMessage == null)
{
Expand All @@ -125,8 +119,7 @@ public static bool HasRequestHeader(this HttpRequestMessage httpRequestMessage,
/// <param name="headerName">The name of the header to locate on the request.</param>
/// <param name="headerValue">The value the header should have. Wildcard is supported.</param>
/// <returns>true when the request contains a header with the specified name and value; otherwise, false.</returns>
[Obsolete("This method is not intended for public use and will be marked internal in the next release.")]
public static bool HasRequestHeader(this HttpRequestMessage httpRequestMessage, string headerName, string headerValue)
internal static bool HasRequestHeader(this HttpRequestMessage httpRequestMessage, string headerName, string headerValue)
{
if (httpRequestMessage == null)
{
Expand All @@ -153,8 +146,7 @@ public static bool HasRequestHeader(this HttpRequestMessage httpRequestMessage,
/// <param name="httpRequestMessage">A <see cref="HttpRequestMessage"/> to check the content header on.</param>
/// <param name="headerName">The name of the header to locate on the request content.</param>
/// <returns>true when the request contains a header with the specified name; otherwise, false.</returns>
[Obsolete("This method is not intended for public use and will be marked internal in the next release.")]
public static bool HasContentHeader(this HttpRequestMessage httpRequestMessage, string headerName)
internal static bool HasContentHeader(this HttpRequestMessage httpRequestMessage, string headerName)
{
if (httpRequestMessage == null)
{
Expand Down Expand Up @@ -182,8 +174,7 @@ public static bool HasContentHeader(this HttpRequestMessage httpRequestMessage,
/// <param name="headerName">The name of the header to locate on the request content.</param>
/// <param name="headerValue">The value the header should have. Wildcard is supported.</param>
/// <returns>true when the request contains a header with the specified name and value; otherwise, false.</returns>
[Obsolete("This method is not intended for public use and will be marked internal in the next release.")]
public static bool HasContentHeader(this HttpRequestMessage httpRequestMessage, string headerName, string headerValue)
internal static bool HasContentHeader(this HttpRequestMessage httpRequestMessage, string headerName, string headerValue)
{
if (httpRequestMessage == null)
{
Expand Down Expand Up @@ -214,8 +205,7 @@ public static bool HasContentHeader(this HttpRequestMessage httpRequestMessage,
/// <param name="httpRequestMessage">A <see cref="HttpRequestMessage"/> to check the correct uri on.</param>
/// <param name="pattern">A pattern to match with the request uri, supports * as wildcards.</param>
/// <returns>true when the request uri matches the pattern; otherwise, false.</returns>
[Obsolete("This method is not intended for public use and will be marked internal in the next release.")]
public static bool HasMatchingUri(this HttpRequestMessage httpRequestMessage, string pattern, bool ignoreCase = true)
internal static bool HasMatchingUri(this HttpRequestMessage httpRequestMessage, string pattern, bool ignoreCase = true)
{
if (httpRequestMessage == null)
{
Expand All @@ -241,8 +231,7 @@ public static bool HasMatchingUri(this HttpRequestMessage httpRequestMessage, st
/// </summary>
/// <param name="httpRequestMessage">A <see cref="HttpRequestMessage"/> to check for content.</param>
/// <returns>true when the request has content; otherwise, false.</returns>
[Obsolete("This method is not intended for public use and will be marked internal in the next release.")]
public static bool HasContent(this HttpRequestMessage httpRequestMessage)
internal static bool HasContent(this HttpRequestMessage httpRequestMessage)
{
if (httpRequestMessage == null)
{
Expand All @@ -258,8 +247,7 @@ public static bool HasContent(this HttpRequestMessage httpRequestMessage)
/// <param name="httpRequestMessage">A <see cref="HttpRequestMessage"/> to check the correct content on.</param>
/// <param name="pattern">A pattern to match the request content, supports * as wildcards.</param>
/// <returns>true when the request content matches the pattern; otherwise, false.</returns>
[Obsolete("This method is not intended for public use and will be marked internal in the next release.")]
public static bool HasContent(this HttpRequestMessage httpRequestMessage, string pattern)
internal static bool HasContent(this HttpRequestMessage httpRequestMessage, string pattern)
{
if (httpRequestMessage == null)
{
Expand Down Expand Up @@ -292,8 +280,7 @@ public static bool HasContent(this HttpRequestMessage httpRequestMessage, string
/// <param name="httpRequestMessage">A <see cref="HttpRequestMessage"/> to check the correct request uir querystring on.</param>
/// <param name="pattern">A pattern to match the request uri querystring, supports * as wildcards.</param>
/// <returns>true when the request uri querystring matches the pattern; otherwise, false.</returns>
[Obsolete("This method is not intended for public use and will be marked internal in the next release.")]
public static bool HasQueryString(this HttpRequestMessage httpRequestMessage, string pattern)
internal static bool HasQueryString(this HttpRequestMessage httpRequestMessage, string pattern)
{
if (httpRequestMessage == null)
{
Expand Down
26 changes: 0 additions & 26 deletions src/TestableHttpClient/HttpRequestMessagesCheckExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -42,9 +42,7 @@ private static IHttpRequestMessagesCheck WithRequestUri(this IHttpRequestMessage
condition = $"uri pattern '{pattern}'";
}

#pragma warning disable CS0618 // Type or member is obsolete
return check.WithFilter(x => x.HasMatchingUri(pattern, ignoreCase), expectedNumberOfRequests, condition);
#pragma warning restore CS0618 // Type or member is obsolete
}

/// <summary>
Expand Down Expand Up @@ -83,9 +81,7 @@ private static IHttpRequestMessagesCheck WithQueryString(this IHttpRequestMessag
_ => $"querystring pattern '{pattern}'"
};

#pragma warning disable CS0618 // Type or member is obsolete
return check.WithFilter(x => x.HasQueryString(pattern), expectedNumberOfRequests, condition);
#pragma warning restore CS0618 // Type or member is obsolete
}

/// <summary>
Expand Down Expand Up @@ -117,9 +113,7 @@ private static IHttpRequestMessagesCheck WithHttpMethod(this IHttpRequestMessage
throw new ArgumentNullException(nameof(httpMethod));
}

#pragma warning disable CS0618 // Type or member is obsolete
return check.WithFilter(x => x.HasHttpMethod(httpMethod), expectedNumberOfRequests, $"HTTP Method '{httpMethod}'");
#pragma warning restore CS0618 // Type or member is obsolete
}

/// <summary>
Expand Down Expand Up @@ -151,9 +145,7 @@ private static IHttpRequestMessagesCheck WithHttpVersion(this IHttpRequestMessag
throw new ArgumentNullException(nameof(httpVersion));
}

#pragma warning disable CS0618 // Type or member is obsolete
return check.WithFilter(x => x.HasHttpVersion(httpVersion), expectedNumberOfRequests, $"HTTP Version '{httpVersion}'");
#pragma warning restore CS0618 // Type or member is obsolete
}

/// <summary>
Expand Down Expand Up @@ -187,9 +179,7 @@ private static IHttpRequestMessagesCheck WithRequestHeader(this IHttpRequestMess
throw new ArgumentNullException(nameof(headerName));
}

#pragma warning disable CS0618 // Type or member is obsolete
return check.WithFilter(x => x.HasRequestHeader(headerName), expectedNumberOfRequests, $"request header '{headerName}'");
#pragma warning restore CS0618 // Type or member is obsolete
}

/// <summary>
Expand Down Expand Up @@ -230,9 +220,7 @@ private static IHttpRequestMessagesCheck WithRequestHeader(this IHttpRequestMess
throw new ArgumentNullException(nameof(headerValue));
}

#pragma warning disable CS0618 // Type or member is obsolete
return check.WithFilter(x => x.HasRequestHeader(headerName, headerValue), expectedNumberOfRequests, $"request header '{headerName}' and value '{headerValue}'");
#pragma warning restore CS0618 // Type or member is obsolete
}

/// <summary>
Expand Down Expand Up @@ -266,9 +254,7 @@ private static IHttpRequestMessagesCheck WithContentHeader(this IHttpRequestMess
throw new ArgumentNullException(nameof(headerName));
}

#pragma warning disable CS0618 // Type or member is obsolete
return check.WithFilter(x => x.HasContentHeader(headerName), expectedNumberOfRequests, $"content header '{headerName}'");
#pragma warning restore CS0618 // Type or member is obsolete
}

/// <summary>
Expand Down Expand Up @@ -309,9 +295,7 @@ private static IHttpRequestMessagesCheck WithContentHeader(this IHttpRequestMess
throw new ArgumentNullException(nameof(headerValue));
}

#pragma warning disable CS0618 // Type or member is obsolete
return check.WithFilter(x => x.HasContentHeader(headerName, headerValue), expectedNumberOfRequests, $"content header '{headerName}' and value '{headerValue}'");
#pragma warning restore CS0618 // Type or member is obsolete
}

/// <summary>
Expand Down Expand Up @@ -343,9 +327,7 @@ private static IHttpRequestMessagesCheck WithHeader(this IHttpRequestMessagesChe
throw new ArgumentNullException(nameof(headerName));
}

#pragma warning disable CS0618 // Type or member is obsolete
return check.WithFilter(x => x.HasRequestHeader(headerName) || x.HasContentHeader(headerName), expectedNumberOfRequests, $"header '{headerName}'");
#pragma warning restore CS0618 // Type or member is obsolete
}

/// <summary>
Expand Down Expand Up @@ -384,9 +366,7 @@ private static IHttpRequestMessagesCheck WithHeader(this IHttpRequestMessagesChe
throw new ArgumentNullException(nameof(headerValue));
}

#pragma warning disable CS0618 // Type or member is obsolete
return check.WithFilter(x => x.HasRequestHeader(headerName, headerValue) || x.HasContentHeader(headerName, headerValue), expectedNumberOfRequests, $"header '{headerName}' and value '{headerValue}'");
#pragma warning restore CS0618 // Type or member is obsolete
}

/// <summary>
Expand Down Expand Up @@ -420,9 +400,7 @@ private static IHttpRequestMessagesCheck WithContent(this IHttpRequestMessagesCh
throw new ArgumentNullException(nameof(pattern));
}

#pragma warning disable CS0618 // Type or member is obsolete
return check.WithFilter(x => x.HasContent(pattern), expectedNumberOfRequests, $"content '{pattern}'");
#pragma warning restore CS0618 // Type or member is obsolete
}

/// <summary>
Expand Down Expand Up @@ -472,9 +450,7 @@ private static IHttpRequestMessagesCheck WithJsonContent(this IHttpRequestMessag

var jsonString = JsonSerializer.Serialize(jsonObject, jsonSerializerOptions ?? check.Options.JsonSerializerOptions);

#pragma warning disable CS0618 // Type or member is obsolete
return check.WithFilter(x => x.HasContent(jsonString) && x.HasContentHeader("Content-Type", "application/json*"), expectedNumberOfRequests, $"json content '{jsonString}'");
#pragma warning restore CS0618 // Type or member is obsolete
}

/// <summary>
Expand Down Expand Up @@ -511,8 +487,6 @@ private static IHttpRequestMessagesCheck WithFormUrlEncodedContent(this IHttpReq
using var content = new FormUrlEncodedContent(nameValueCollection);
var contentString = content.ReadAsStringAsync().Result;

#pragma warning disable CS0618 // Type or member is obsolete
return check.WithFilter(x => x.HasContent(contentString) && x.HasContentHeader("Content-Type", "application/x-www-form-urlencoded*"), expectedNumberOfRequests, $"form url encoded content '{contentString}'");
#pragma warning restore CS0618 // Type or member is obsolete
}
}
Loading