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
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,24 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [0.12] - unplanned
### Deprecated
- The methods `WithRequestHeader` and `WithContentHeader` are deprecated, please use `WithHeader` instead.
- The `WithFilter` method is deprecated and will be made internal or be removed. If there is a specific assertion that is missing, please open an issue.

### Removed
- .NET 6.0 target, since it is no longer supported
- .NET Framework 4.6.2, 4.7.0 and 4.7.2, since these can't be tested using xUnit v3
- automatic nuget updates by dependabot, since we want to test against the lowest supported nuget version and most of the time dependabot does not choose the right package.

### Added
- Support for .NET 9.0
- Support for .NET 10.0

### Changed
- The TestableHttpMessageHandler now makes a clone of the original request, so that the original request can be disposed.
This change also makes it possible to assert the content on .NET Framework.
- The methods `WithRequestHeader` and `WithContentHeader` now work the same as `WithHeader`, this might lead to slight changes in the behavior since now the headers from both the request and the content of that request are checked.
- Moved `WithHttpMethod`, `WithRequestUri`, `WithHttpVersion`, `WithHeader` and `WithContent` from `HttpRequestMessagesCheckExtensions` to `HttpRequestMessageAsserter` and specify them on the `IHttpRequestMessagesCheck` interface.

## [0.11] - 2024-06-15
### Removed
Expand Down
144 changes: 144 additions & 0 deletions src/TestableHttpClient/HttpRequestMessageAsserter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -81,4 +81,148 @@ public IHttpRequestMessagesCheck WithFilter(Func<HttpRequestMessage, bool> reque
}
return this;
}

/// <summary>
/// Asserts whether requests were made to a given URI based on a pattern.
/// </summary>
/// <param name="pattern">The uri pattern that is expected.</param>
/// <returns>The <seealso cref="IHttpRequestMessagesCheck"/> for further assertions.</returns>
public IHttpRequestMessagesCheck WithRequestUri(string pattern) => WithRequestUri(pattern, null);

/// <summary>
/// Asserts whether requests were made to a given URI based on a pattern.
/// </summary>
/// <param name="pattern">The uri pattern that is expected.</param>
/// <param name="expectedNumberOfRequests">The expected number of requests.</param>
/// <returns>The <seealso cref="IHttpRequestMessagesCheck"/> for further assertions.</returns>
public IHttpRequestMessagesCheck WithRequestUri(string pattern, int expectedNumberOfRequests) => WithRequestUri(pattern, (int?)expectedNumberOfRequests);

private IHttpRequestMessagesCheck WithRequestUri(string pattern, int? expectedNumberOfRequests)
{
Guard.ThrowIfNullOrEmpty(pattern);

var condition = string.Empty;
if (pattern != "*")
{
condition = $"uri pattern '{pattern}'";
}

UriPattern uriPattern = UriPatternParser.Parse(pattern);

return WithFilter(x => x.RequestUri is not null && uriPattern.Matches(x.RequestUri, Options.UriPatternMatchingOptions), expectedNumberOfRequests, condition);
}

/// <summary>
/// Asserts whether requests were made with a given HTTP Method.
/// </summary>
/// <param name="httpMethod">The <seealso cref="HttpMethod"/> that is expected.</param>
/// <returns>The <seealso cref="IHttpRequestMessagesCheck"/> for further assertions.</returns>
public IHttpRequestMessagesCheck WithHttpMethod(HttpMethod httpMethod) => WithHttpMethod(httpMethod, null);

/// <summary>
/// Asserts whether requests were made with a given HTTP Method.
/// </summary>
/// <param name="httpMethod">The <seealso cref="HttpMethod"/> that is expected.</param>
/// <param name="expectedNumberOfRequests">The expected number of requests.</param>
/// <returns>The <seealso cref="IHttpRequestMessagesCheck"/> for further assertions.</returns>
public IHttpRequestMessagesCheck WithHttpMethod(HttpMethod httpMethod, int expectedNumberOfRequests) => WithHttpMethod(httpMethod, (int?)expectedNumberOfRequests);

private IHttpRequestMessagesCheck WithHttpMethod(HttpMethod httpMethod, int? expectedNumberOfRequests)
{
Guard.ThrowIfNull(httpMethod);

return WithFilter(x => x.HasHttpMethod(httpMethod), expectedNumberOfRequests, $"HTTP Method '{httpMethod}'");
}

/// <summary>
/// Asserts whether requests were made using a specific HTTP Version.
/// </summary>
/// <param name="check">The implementation that hold all the request messages.</param>
/// <param name="httpVersion">The <seealso cref="System.Net.HttpVersion"/> that is expected.</param>
/// <returns>The <seealso cref="IHttpRequestMessagesCheck"/> for further assertions.</returns>
public IHttpRequestMessagesCheck WithHttpVersion(Version httpVersion) => WithHttpVersion(httpVersion, null);

/// <summary>
/// Asserts whether requests were made using a specific HTTP Version.
/// </summary>
/// <param name="httpVersion">The <seealso cref="System.Net.HttpVersion"/> that is expected.</param>
/// <param name="expectedNumberOfRequests">The expected number of requests.</param>
/// <returns>The <seealso cref="IHttpRequestMessagesCheck"/> for further assertions.</returns>
public IHttpRequestMessagesCheck WithHttpVersion(Version httpVersion, int expectedNumberOfRequests) => WithHttpVersion(httpVersion, (int?)expectedNumberOfRequests);

private IHttpRequestMessagesCheck WithHttpVersion(Version httpVersion, int? expectedNumberOfRequests)
{
Guard.ThrowIfNull(httpVersion);

return WithFilter(x => x.HasHttpVersion(httpVersion), expectedNumberOfRequests, $"HTTP Version '{httpVersion}'");
}

/// <summary>
/// Asserts whether requests were made with a specific header name. Values are ignored.
/// </summary>
/// <param name="headerName">The name of the header that is expected.</param>
/// <returns>The <seealso cref="IHttpRequestMessagesCheck"/> for further assertions.</returns>
public IHttpRequestMessagesCheck WithHeader(string headerName) => WithHeader(headerName, (int?)null);

/// <summary>
/// Asserts whether requests were made with a specific header name. Values are ignored.
/// </summary>
/// <param name="headerName">The name of the header that is expected.</param>
/// <param name="expectedNumberOfRequests">The expected number of requests.</param>
/// <returns>The <seealso cref="IHttpRequestMessagesCheck"/> for further assertions.</returns>
public IHttpRequestMessagesCheck WithHeader(string headerName, int expectedNumberOfRequests) => WithHeader(headerName, (int?)expectedNumberOfRequests);

private IHttpRequestMessagesCheck WithHeader(string headerName, int? expectedNumberOfRequests)
{
Guard.ThrowIfNullOrEmpty(headerName);

return WithFilter(x => x.HasHeader(headerName), expectedNumberOfRequests, $"header '{headerName}'");
}

/// <summary>
/// Asserts whether requests were made with a specific header name and value.
/// </summary>
/// <param name="headerName">The name of the header that is expected.</param>
/// <param name="headerValue">The value of the expected header, supports wildcards.</param>
/// <returns>The <seealso cref="IHttpRequestMessagesCheck"/> for further assertions.</returns>
public IHttpRequestMessagesCheck WithHeader(string headerName, string headerValue) => WithHeader(headerName, headerValue, null);

/// <summary>
/// Asserts whether requests were made with a specific header name and value.
/// </summary>
/// <param name="headerName">The name of the header that is expected.</param>
/// <param name="headerValue">The value of the expected header, supports wildcards.</param>
/// <param name="expectedNumberOfRequests">The expected number of requests.</param>
/// <returns>The <seealso cref="IHttpRequestMessagesCheck"/> for further assertions.</returns>
public IHttpRequestMessagesCheck WithHeader(string headerName, string headerValue, int expectedNumberOfRequests) => WithHeader(headerName, headerValue, (int?)expectedNumberOfRequests);

private IHttpRequestMessagesCheck WithHeader(string headerName, string headerValue, int? expectedNumberOfRequests)
{
Guard.ThrowIfNullOrEmpty(headerName);
Guard.ThrowIfNullOrEmpty(headerValue);

return WithFilter(x => x.HasHeader(headerName, headerValue), expectedNumberOfRequests, $"header '{headerName}' and value '{headerValue}'");
}

/// <summary>
/// Asserts whether requests were made with specific content.
/// </summary>
/// <param name="pattern">The expected content, supports wildcards.</param>
/// <returns>The <seealso cref="IHttpRequestMessagesCheck"/> for further assertions.</returns>
public IHttpRequestMessagesCheck WithContent(string pattern) => WithContent(pattern, null);

/// <summary>
/// Asserts whether requests were made with specific content.
/// </summary>
/// <param name="pattern">The expected content, supports wildcards.</param>
/// <param name="expectedNumberOfRequests">The expected number of requests.</param>
/// <returns>The <seealso cref="IHttpRequestMessagesCheck"/> for further assertions.</returns>
public IHttpRequestMessagesCheck WithContent(string pattern, int expectedNumberOfRequests) => WithContent(pattern, (int?)expectedNumberOfRequests);

private IHttpRequestMessagesCheck WithContent(string pattern, int? expectedNumberOfRequests)
{
Guard.ThrowIfNull(pattern);

return WithFilter(x => x.HasContent(pattern), expectedNumberOfRequests, $"content '{pattern}'");
}
}
86 changes: 20 additions & 66 deletions src/TestableHttpClient/HttpRequestMessageExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,20 +19,6 @@ internal static bool HasHttpVersion(this HttpRequestMessage httpRequestMessage,
return httpRequestMessage.Version == httpVersion;
}

/// <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>
internal static bool HasHttpVersion(this HttpRequestMessage httpRequestMessage, string httpVersion)
{
Guard.ThrowIfNull(httpRequestMessage);
Guard.ThrowIfNullOrEmpty(httpVersion);

return httpRequestMessage.HasHttpVersion(new Version(httpVersion));
}

/// <summary>
/// Determines whether a specific HttpMethod is set on a request.
/// </summary>
Expand All @@ -47,27 +33,24 @@ internal static bool HasHttpMethod(this HttpRequestMessage httpRequestMessage, H
return httpRequestMessage.Method == httpMethod;
}

/// <summary>
/// Determines whether a specific HttpMethod is set on a request.
/// </summary>
/// <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>
internal static bool HasHttpMethod(this HttpRequestMessage httpRequestMessage, string httpMethod)
internal static bool HasHeader(this HttpRequestMessage httpRequestMessage, string headerName)
{
Guard.ThrowIfNull(httpRequestMessage);
Guard.ThrowIfNullOrEmpty(httpMethod);
Guard.ThrowIfNullOrEmpty(headerName);

return httpRequestMessage.HasHttpMethod(new HttpMethod(httpMethod));
return httpRequestMessage.Headers.HasHeader(headerName) || (httpRequestMessage.Content is not null && httpRequestMessage.Content.Headers.HasHeader(headerName));
}

/// <summary>
/// Determines whether a specific header is set on a request.
/// </summary>
/// <remarks>This method only checks headers in <see cref="HttpRequestHeaders"/></remarks>
/// <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>
internal static bool HasHeader(this HttpRequestMessage httpRequestMessage, string headerName, string headerValue)
{
Guard.ThrowIfNull(httpRequestMessage);
Guard.ThrowIfNullOrEmpty(headerName);
Guard.ThrowIfNullOrEmpty(headerValue);

return httpRequestMessage.Headers.HasHeader(headerName, headerValue) || (httpRequestMessage.Content is not null && httpRequestMessage.Content.Headers.HasHeader(headerName, headerValue));
}

[Obsolete("Use HasHeader instead.")]
internal static bool HasRequestHeader(this HttpRequestMessage httpRequestMessage, string headerName)
{
Guard.ThrowIfNull(httpRequestMessage);
Expand All @@ -76,14 +59,7 @@ internal static bool HasRequestHeader(this HttpRequestMessage httpRequestMessage
return httpRequestMessage.Headers.HasHeader(headerName);
}

/// <summary>
/// Determines whether a specific header with a specific value is set on a request.
/// </summary>
/// <remarks>This method only checks headers in <see cref="HttpRequestHeaders"/></remarks>
/// <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>
/// <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("Use HasHeader instead.")]
internal static bool HasRequestHeader(this HttpRequestMessage httpRequestMessage, string headerName, string headerValue)
{
Guard.ThrowIfNull(httpRequestMessage);
Expand All @@ -93,13 +69,7 @@ internal static bool HasRequestHeader(this HttpRequestMessage httpRequestMessage
return httpRequestMessage.Headers.HasHeader(headerName, headerValue);
}

/// <summary>
/// Determines whether a specific header is set on a request.
/// </summary>
/// <remarks>This method only checks headers in <see cref="HttpContentHeaders"/></remarks>
/// <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("Use HasHeader instead.")]
internal static bool HasContentHeader(this HttpRequestMessage httpRequestMessage, string headerName)
{
Guard.ThrowIfNull(httpRequestMessage);
Expand All @@ -113,14 +83,7 @@ internal static bool HasContentHeader(this HttpRequestMessage httpRequestMessage
return httpRequestMessage.Content.Headers.HasHeader(headerName);
}

/// <summary>
/// Determines whether a specific header with a specific value is set on a request.
/// </summary>
/// <remarks>This method only checks headers in <see cref="HttpContentHeaders"/></remarks>
/// <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>
/// <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("Use HasHeader instead.")]
internal static bool HasContentHeader(this HttpRequestMessage httpRequestMessage, string headerName, string headerValue)
{
Guard.ThrowIfNull(httpRequestMessage);
Expand All @@ -135,18 +98,6 @@ internal static bool HasContentHeader(this HttpRequestMessage httpRequestMessage
return httpRequestMessage.Content.Headers.HasHeader(headerName, headerValue);
}

/// <summary>
/// Determines whether the request has content.
/// </summary>
/// <param name="httpRequestMessage">A <see cref="HttpRequestMessage"/> to check for content.</param>
/// <returns>true when the request has content; otherwise, false.</returns>
internal static bool HasContent(this HttpRequestMessage httpRequestMessage)
{
Guard.ThrowIfNull(httpRequestMessage);

return httpRequestMessage.Content != null;
}

/// <summary>
/// Determines whether the request content matches a string pattern.
/// </summary>
Expand All @@ -163,7 +114,10 @@ internal static bool HasContent(this HttpRequestMessage httpRequestMessage, stri
return false;
}

var stringContent = httpRequestMessage.Content.ReadAsStringAsync().Result;
var stringContent = httpRequestMessage.Content.ReadAsStringAsync()
.ConfigureAwait(false)
.GetAwaiter()
.GetResult();

return pattern switch
{
Expand Down
Loading
Loading