diff --git a/.gitattributes b/.gitattributes
new file mode 100644
index 00000000..a4203ccc
--- /dev/null
+++ b/.gitattributes
@@ -0,0 +1,7 @@
+* text=auto
+*.cs text diff=csharp
+*.cshtml text diff=html
+*.csx text diff=csharp
+*.sln text eol=crlf
+*.slnx text eol=crlf
+*.csproj text eol=crlf
diff --git a/TestableHttpClient.sln b/TestableHttpClient.sln
deleted file mode 100644
index 4f29e03d..00000000
--- a/TestableHttpClient.sln
+++ /dev/null
@@ -1,83 +0,0 @@
-
-Microsoft Visual Studio Solution File, Format Version 12.00
-# Visual Studio Version 17
-VisualStudioVersion = 17.3.32901.215
-MinimumVisualStudioVersion = 15.0.26124.0
-Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{4C8914F8-D732-462B-978E-3BB5DBE547D7}"
-EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TestableHttpClient", "src\TestableHttpClient\TestableHttpClient.csproj", "{FD5111E1-2970-4DC4-84DD-E4966E17DCB3}"
-EndProject
-Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{BBCED492-E92B-4FA8-A4A5-B5A76091F25E}"
-EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TestableHttpClient.Tests", "test\TestableHttpClient.Tests\TestableHttpClient.Tests.csproj", "{70673E72-C346-4AC2-946D-D9F99816FC72}"
-EndProject
-Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{800147F1-758C-406D-AF75-CB20EB7CDB18}"
- ProjectSection(SolutionItems) = preProject
- .editorconfig = .editorconfig
- CHANGELOG.md = CHANGELOG.md
- LICENSE = LICENSE
- README.md = README.md
- version.json = version.json
- EndProjectSection
-EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TestableHttpClient.IntegrationTests", "test\TestableHttpClient.IntegrationTests\TestableHttpClient.IntegrationTests.csproj", "{37A6C1C0-1117-43DE-BD15-290BC8AD32BE}"
-EndProject
-Global
- GlobalSection(SolutionConfigurationPlatforms) = preSolution
- Debug|Any CPU = Debug|Any CPU
- Debug|x64 = Debug|x64
- Debug|x86 = Debug|x86
- Release|Any CPU = Release|Any CPU
- Release|x64 = Release|x64
- Release|x86 = Release|x86
- EndGlobalSection
- GlobalSection(ProjectConfigurationPlatforms) = postSolution
- {FD5111E1-2970-4DC4-84DD-E4966E17DCB3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {FD5111E1-2970-4DC4-84DD-E4966E17DCB3}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {FD5111E1-2970-4DC4-84DD-E4966E17DCB3}.Debug|x64.ActiveCfg = Debug|Any CPU
- {FD5111E1-2970-4DC4-84DD-E4966E17DCB3}.Debug|x64.Build.0 = Debug|Any CPU
- {FD5111E1-2970-4DC4-84DD-E4966E17DCB3}.Debug|x86.ActiveCfg = Debug|Any CPU
- {FD5111E1-2970-4DC4-84DD-E4966E17DCB3}.Debug|x86.Build.0 = Debug|Any CPU
- {FD5111E1-2970-4DC4-84DD-E4966E17DCB3}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {FD5111E1-2970-4DC4-84DD-E4966E17DCB3}.Release|Any CPU.Build.0 = Release|Any CPU
- {FD5111E1-2970-4DC4-84DD-E4966E17DCB3}.Release|x64.ActiveCfg = Release|Any CPU
- {FD5111E1-2970-4DC4-84DD-E4966E17DCB3}.Release|x64.Build.0 = Release|Any CPU
- {FD5111E1-2970-4DC4-84DD-E4966E17DCB3}.Release|x86.ActiveCfg = Release|Any CPU
- {FD5111E1-2970-4DC4-84DD-E4966E17DCB3}.Release|x86.Build.0 = Release|Any CPU
- {70673E72-C346-4AC2-946D-D9F99816FC72}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {70673E72-C346-4AC2-946D-D9F99816FC72}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {70673E72-C346-4AC2-946D-D9F99816FC72}.Debug|x64.ActiveCfg = Debug|Any CPU
- {70673E72-C346-4AC2-946D-D9F99816FC72}.Debug|x64.Build.0 = Debug|Any CPU
- {70673E72-C346-4AC2-946D-D9F99816FC72}.Debug|x86.ActiveCfg = Debug|Any CPU
- {70673E72-C346-4AC2-946D-D9F99816FC72}.Debug|x86.Build.0 = Debug|Any CPU
- {70673E72-C346-4AC2-946D-D9F99816FC72}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {70673E72-C346-4AC2-946D-D9F99816FC72}.Release|Any CPU.Build.0 = Release|Any CPU
- {70673E72-C346-4AC2-946D-D9F99816FC72}.Release|x64.ActiveCfg = Release|Any CPU
- {70673E72-C346-4AC2-946D-D9F99816FC72}.Release|x64.Build.0 = Release|Any CPU
- {70673E72-C346-4AC2-946D-D9F99816FC72}.Release|x86.ActiveCfg = Release|Any CPU
- {70673E72-C346-4AC2-946D-D9F99816FC72}.Release|x86.Build.0 = Release|Any CPU
- {37A6C1C0-1117-43DE-BD15-290BC8AD32BE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {37A6C1C0-1117-43DE-BD15-290BC8AD32BE}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {37A6C1C0-1117-43DE-BD15-290BC8AD32BE}.Debug|x64.ActiveCfg = Debug|Any CPU
- {37A6C1C0-1117-43DE-BD15-290BC8AD32BE}.Debug|x64.Build.0 = Debug|Any CPU
- {37A6C1C0-1117-43DE-BD15-290BC8AD32BE}.Debug|x86.ActiveCfg = Debug|Any CPU
- {37A6C1C0-1117-43DE-BD15-290BC8AD32BE}.Debug|x86.Build.0 = Debug|Any CPU
- {37A6C1C0-1117-43DE-BD15-290BC8AD32BE}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {37A6C1C0-1117-43DE-BD15-290BC8AD32BE}.Release|Any CPU.Build.0 = Release|Any CPU
- {37A6C1C0-1117-43DE-BD15-290BC8AD32BE}.Release|x64.ActiveCfg = Release|Any CPU
- {37A6C1C0-1117-43DE-BD15-290BC8AD32BE}.Release|x64.Build.0 = Release|Any CPU
- {37A6C1C0-1117-43DE-BD15-290BC8AD32BE}.Release|x86.ActiveCfg = Release|Any CPU
- {37A6C1C0-1117-43DE-BD15-290BC8AD32BE}.Release|x86.Build.0 = Release|Any CPU
- EndGlobalSection
- GlobalSection(SolutionProperties) = preSolution
- HideSolutionNode = FALSE
- EndGlobalSection
- GlobalSection(NestedProjects) = preSolution
- {FD5111E1-2970-4DC4-84DD-E4966E17DCB3} = {4C8914F8-D732-462B-978E-3BB5DBE547D7}
- {70673E72-C346-4AC2-946D-D9F99816FC72} = {BBCED492-E92B-4FA8-A4A5-B5A76091F25E}
- {37A6C1C0-1117-43DE-BD15-290BC8AD32BE} = {BBCED492-E92B-4FA8-A4A5-B5A76091F25E}
- EndGlobalSection
- GlobalSection(ExtensibilityGlobals) = postSolution
- SolutionGuid = {CD31CAB7-6661-4E80-9A70-BC8BA6B9B764}
- EndGlobalSection
-EndGlobal
diff --git a/TestableHttpClient.slnx b/TestableHttpClient.slnx
new file mode 100644
index 00000000..c2c7d77e
--- /dev/null
+++ b/TestableHttpClient.slnx
@@ -0,0 +1,29 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/global.json b/global.json
index 3b9f8d5b..0b3d6623 100644
--- a/global.json
+++ b/global.json
@@ -3,5 +3,8 @@
"version": "10.0.100",
"allowPrerelease": false,
"rollForward": "latestMajor"
+ },
+ "test": {
+ "runner": "Microsoft.Testing.Platform"
}
}
diff --git a/src/TestableHttpClient/HttpHeadersExtensions.cs b/src/TestableHttpClient/HttpHeadersExtensions.cs
index 8b3d152f..9ea4490c 100644
--- a/src/TestableHttpClient/HttpHeadersExtensions.cs
+++ b/src/TestableHttpClient/HttpHeadersExtensions.cs
@@ -7,14 +7,13 @@ internal static bool HasHeader(this HttpHeaders headers, string headerName)
return headers.TryGetValues(headerName, out _);
}
- internal static bool HasHeader(this HttpHeaders headers, string headerName, string headerValue)
+ internal static bool HasHeader(this HttpHeaders headers, string headerName, Value headerValue)
{
if (headers.TryGetValues(headerName, out var values))
{
var value = string.Join(" ", values);
- return StringMatcher.Matches(value, headerValue);
+ return headerValue.Matches(value, false);
}
-
return false;
}
}
diff --git a/src/TestableHttpClient/HttpRequestMessageAsserter.cs b/src/TestableHttpClient/HttpRequestMessageAsserter.cs
index 3559981d..5b1dc1d0 100644
--- a/src/TestableHttpClient/HttpRequestMessageAsserter.cs
+++ b/src/TestableHttpClient/HttpRequestMessageAsserter.cs
@@ -7,6 +7,8 @@ internal sealed class HttpRequestMessageAsserter : IHttpRequestMessagesCheck
{
private readonly List _expectedConditions = new();
+ private readonly RequestBuilder expectedRequestBuilder;
+
///
/// Construct a new HttpRequestMessageAsserter.
///
@@ -16,6 +18,7 @@ public HttpRequestMessageAsserter(IEnumerable httpRequestMes
{
Requests = httpRequestMessages ?? throw new ArgumentNullException(nameof(httpRequestMessages));
Options = options ?? new TestableHttpMessageHandlerOptions();
+ expectedRequestBuilder = new RequestBuilder(Options.UriPatternMatchingOptions);
}
///
@@ -27,9 +30,28 @@ public HttpRequestMessageAsserter(IEnumerable httpRequestMes
///
public TestableHttpMessageHandlerOptions Options { get; }
- private void Assert(int? expectedCount = null)
+ private HttpRequestMessageAsserter Assert(int? expectedCount = null, string condition = "")
{
- var actualCount = Requests.Count();
+ if (!string.IsNullOrEmpty(condition))
+ {
+ _expectedConditions.Add(condition);
+ }
+ return Assert(expectedCount);
+ }
+
+ private HttpRequestMessageAsserter Assert(int? expectedCount = null)
+ {
+ int actualCount;
+ Request expectedRequest = expectedRequestBuilder.Build();
+ try
+ {
+ actualCount = Requests.Count(expectedRequest.Equals);
+ }
+ catch (ObjectDisposedException)
+ {
+ throw new HttpRequestMessageAssertionException("Can't validate requests, because one or more requests have content that is already disposed.");
+ }
+
var pass = expectedCount switch
{
null => actualCount > 0,
@@ -38,9 +60,11 @@ private void Assert(int? expectedCount = null)
if (!pass)
{
- var message = MessageBuilder.BuildMessage(expectedCount, actualCount, _expectedConditions);
+ var message = MessageBuilder.BuildMessage(expectedCount, actualCount, expectedRequest, _expectedConditions);
throw new HttpRequestMessageAssertionException(message);
}
+
+ return this;
}
///
@@ -50,6 +74,7 @@ private void Assert(int? expectedCount = null)
/// The name of the condition, used in the exception message.
/// The for further assertions.
[AssertionMethod]
+ [Obsolete("WithFilter will be made internal, since it should no longer be necesary to use.")]
public IHttpRequestMessagesCheck WithFilter(Func requestFilter, string condition) => WithFilter(requestFilter, null, condition);
///
@@ -60,9 +85,11 @@ private void Assert(int? expectedCount = null)
/// The name of the condition, used in the exception message.
/// The for further assertions.
[AssertionMethod]
+ [Obsolete("WithFilter will be made internal, since it should no longer be necesary to use.")]
public IHttpRequestMessagesCheck WithFilter(Func requestFilter, int expectedNumberOfRequests, string condition) => WithFilter(requestFilter, (int?)expectedNumberOfRequests, condition);
[AssertionMethod]
+ [Obsolete("WithFilter will be made internal, since it should no longer be necesary to use.")]
public IHttpRequestMessagesCheck WithFilter(Func requestFilter, int? expectedNumberOfRequests, string condition)
{
if (!string.IsNullOrEmpty(condition))
@@ -70,15 +97,9 @@ public IHttpRequestMessagesCheck WithFilter(Func reque
_expectedConditions.Add(condition);
}
- try
- {
- Requests = Requests.Where(requestFilter);
- Assert(expectedNumberOfRequests);
- }
- catch (ObjectDisposedException)
- {
- throw new HttpRequestMessageAssertionException("Can't validate requests, because one or more requests have content that is already disposed.");
- }
+ Requests = Requests.Where(requestFilter);
+ Assert(expectedNumberOfRequests);
+
return this;
}
@@ -97,19 +118,13 @@ public IHttpRequestMessagesCheck WithFilter(Func reque
/// The for further assertions.
public IHttpRequestMessagesCheck WithRequestUri(string pattern, int expectedNumberOfRequests) => WithRequestUri(pattern, (int?)expectedNumberOfRequests);
- private IHttpRequestMessagesCheck WithRequestUri(string pattern, int? expectedNumberOfRequests)
+ private HttpRequestMessageAsserter WithRequestUri(string pattern, int? expectedNumberOfRequests)
{
Guard.ThrowIfNullOrEmpty(pattern);
- var condition = string.Empty;
- if (pattern != "*")
- {
- condition = $"uri pattern '{pattern}'";
- }
+ expectedRequestBuilder.WithRequestUri(pattern);
- UriPattern uriPattern = UriPatternParser.Parse(pattern);
-
- return WithFilter(x => x.RequestUri is not null && uriPattern.Matches(x.RequestUri, Options.UriPatternMatchingOptions), expectedNumberOfRequests, condition);
+ return Assert(expectedNumberOfRequests);
}
///
@@ -127,11 +142,13 @@ private IHttpRequestMessagesCheck WithRequestUri(string pattern, int? expectedNu
/// The for further assertions.
public IHttpRequestMessagesCheck WithHttpMethod(HttpMethod httpMethod, int expectedNumberOfRequests) => WithHttpMethod(httpMethod, (int?)expectedNumberOfRequests);
- private IHttpRequestMessagesCheck WithHttpMethod(HttpMethod httpMethod, int? expectedNumberOfRequests)
+ private HttpRequestMessageAsserter WithHttpMethod(HttpMethod httpMethod, int? expectedNumberOfRequests)
{
Guard.ThrowIfNull(httpMethod);
- return WithFilter(x => x.HasHttpMethod(httpMethod), expectedNumberOfRequests, $"HTTP Method '{httpMethod}'");
+ expectedRequestBuilder.WithMethod(httpMethod);
+
+ return Assert(expectedNumberOfRequests);
}
///
@@ -150,11 +167,13 @@ private IHttpRequestMessagesCheck WithHttpMethod(HttpMethod httpMethod, int? exp
/// The for further assertions.
public IHttpRequestMessagesCheck WithHttpVersion(Version httpVersion, int expectedNumberOfRequests) => WithHttpVersion(httpVersion, (int?)expectedNumberOfRequests);
- private IHttpRequestMessagesCheck WithHttpVersion(Version httpVersion, int? expectedNumberOfRequests)
+ private HttpRequestMessageAsserter WithHttpVersion(Version httpVersion, int? expectedNumberOfRequests)
{
Guard.ThrowIfNull(httpVersion);
- return WithFilter(x => x.HasHttpVersion(httpVersion), expectedNumberOfRequests, $"HTTP Version '{httpVersion}'");
+ expectedRequestBuilder.WithVersion(httpVersion);
+
+ return Assert(expectedNumberOfRequests, $"HTTP Version '{httpVersion}'");
}
///
@@ -172,11 +191,13 @@ private IHttpRequestMessagesCheck WithHttpVersion(Version httpVersion, int? expe
/// The for further assertions.
public IHttpRequestMessagesCheck WithHeader(string headerName, int expectedNumberOfRequests) => WithHeader(headerName, (int?)expectedNumberOfRequests);
- private IHttpRequestMessagesCheck WithHeader(string headerName, int? expectedNumberOfRequests)
+ private HttpRequestMessageAsserter WithHeader(string headerName, int? expectedNumberOfRequests)
{
Guard.ThrowIfNullOrEmpty(headerName);
- return WithFilter(x => x.HasHeader(headerName), expectedNumberOfRequests, $"header '{headerName}'");
+ expectedRequestBuilder.WithHeader(headerName);
+
+ return Assert(expectedNumberOfRequests);
}
///
@@ -196,12 +217,14 @@ private IHttpRequestMessagesCheck WithHeader(string headerName, int? expectedNum
/// The for further assertions.
public IHttpRequestMessagesCheck WithHeader(string headerName, string headerValue, int expectedNumberOfRequests) => WithHeader(headerName, headerValue, (int?)expectedNumberOfRequests);
- private IHttpRequestMessagesCheck WithHeader(string headerName, string headerValue, int? expectedNumberOfRequests)
+ private HttpRequestMessageAsserter 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}'");
+ expectedRequestBuilder.WithHeader(headerName, headerValue);
+
+ return Assert(expectedNumberOfRequests);
}
///
@@ -219,10 +242,12 @@ private IHttpRequestMessagesCheck WithHeader(string headerName, string headerVal
/// The for further assertions.
public IHttpRequestMessagesCheck WithContent(string pattern, int expectedNumberOfRequests) => WithContent(pattern, (int?)expectedNumberOfRequests);
- private IHttpRequestMessagesCheck WithContent(string pattern, int? expectedNumberOfRequests)
+ private HttpRequestMessageAsserter WithContent(string pattern, int? expectedNumberOfRequests)
{
Guard.ThrowIfNull(pattern);
- return WithFilter(x => x.HasContent(pattern), expectedNumberOfRequests, $"content '{pattern}'");
+ expectedRequestBuilder.WithContent(pattern);
+
+ return Assert(expectedNumberOfRequests);
}
}
diff --git a/src/TestableHttpClient/HttpRequestMessageExtensions.cs b/src/TestableHttpClient/HttpRequestMessageExtensions.cs
deleted file mode 100644
index 26abc9a2..00000000
--- a/src/TestableHttpClient/HttpRequestMessageExtensions.cs
+++ /dev/null
@@ -1,129 +0,0 @@
-namespace TestableHttpClient;
-
-///
-/// A set of static methods for checking values on a .
-///
-internal static class HttpRequestMessageExtensions
-{
- ///
- /// Determines whether a specific HttpVersion is set on a request.
- ///
- /// A to check the correct version on.
- /// The expected version.
- /// true when the HttpVersion matches; otherwise, false.
- internal static bool HasHttpVersion(this HttpRequestMessage httpRequestMessage, Version httpVersion)
- {
- Guard.ThrowIfNull(httpRequestMessage);
- Guard.ThrowIfNull(httpVersion);
-
- return httpRequestMessage.Version == httpVersion;
- }
-
- ///
- /// Determines whether a specific HttpMethod is set on a request.
- ///
- /// A to check the correct method on.
- /// The expected method.
- /// true when the HttpMethod matches; otherwise, false.
- internal static bool HasHttpMethod(this HttpRequestMessage httpRequestMessage, HttpMethod httpMethod)
- {
- Guard.ThrowIfNull(httpRequestMessage);
- Guard.ThrowIfNull(httpMethod);
-
- return httpRequestMessage.Method == httpMethod;
- }
-
- internal static bool HasHeader(this HttpRequestMessage httpRequestMessage, string headerName)
- {
- Guard.ThrowIfNull(httpRequestMessage);
- Guard.ThrowIfNullOrEmpty(headerName);
-
- return httpRequestMessage.Headers.HasHeader(headerName) || (httpRequestMessage.Content is not null && httpRequestMessage.Content.Headers.HasHeader(headerName));
- }
-
- 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);
- Guard.ThrowIfNullOrEmpty(headerName);
-
- return httpRequestMessage.Headers.HasHeader(headerName);
- }
-
- [Obsolete("Use HasHeader instead.")]
- internal static bool HasRequestHeader(this HttpRequestMessage httpRequestMessage, string headerName, string headerValue)
- {
- Guard.ThrowIfNull(httpRequestMessage);
- Guard.ThrowIfNullOrEmpty(headerName);
- Guard.ThrowIfNullOrEmpty(headerValue);
-
- return httpRequestMessage.Headers.HasHeader(headerName, headerValue);
- }
-
- [Obsolete("Use HasHeader instead.")]
- internal static bool HasContentHeader(this HttpRequestMessage httpRequestMessage, string headerName)
- {
- Guard.ThrowIfNull(httpRequestMessage);
- Guard.ThrowIfNullOrEmpty(headerName);
-
- if (httpRequestMessage.Content == null)
- {
- return false;
- }
-
- return httpRequestMessage.Content.Headers.HasHeader(headerName);
- }
-
- [Obsolete("Use HasHeader instead.")]
- internal static bool HasContentHeader(this HttpRequestMessage httpRequestMessage, string headerName, string headerValue)
- {
- Guard.ThrowIfNull(httpRequestMessage);
- Guard.ThrowIfNullOrEmpty(headerName);
- Guard.ThrowIfNullOrEmpty(headerValue);
-
- if (httpRequestMessage.Content == null)
- {
- return false;
- }
-
- return httpRequestMessage.Content.Headers.HasHeader(headerName, headerValue);
- }
-
- ///
- /// Determines whether the request content matches a string pattern.
- ///
- /// A to check the correct content on.
- /// A pattern to match the request content, supports * as wildcards.
- /// true when the request content matches the pattern; otherwise, false.
- internal static bool HasContent(this HttpRequestMessage httpRequestMessage, string pattern)
- {
- Guard.ThrowIfNull(httpRequestMessage);
- Guard.ThrowIfNull(pattern);
-
- if (httpRequestMessage.Content == null)
- {
- return false;
- }
-
- var stringContent = httpRequestMessage.Content.ReadAsStringAsync()
- .ConfigureAwait(false)
- .GetAwaiter()
- .GetResult();
-
- return pattern switch
- {
- "" => stringContent == pattern,
- "*" => true,
- _ => StringMatcher.Matches(stringContent, pattern),
- };
- }
-}
diff --git a/src/TestableHttpClient/PublicAPI.Unshipped.txt b/src/TestableHttpClient/PublicAPI.Unshipped.txt
index f9efd0a5..568d353b 100644
--- a/src/TestableHttpClient/PublicAPI.Unshipped.txt
+++ b/src/TestableHttpClient/PublicAPI.Unshipped.txt
@@ -10,4 +10,11 @@ TestableHttpClient.IHttpRequestMessagesCheck.WithHttpMethod(System.Net.Http.Http
TestableHttpClient.IHttpRequestMessagesCheck.WithHttpVersion(System.Version! httpVersion) -> TestableHttpClient.IHttpRequestMessagesCheck!
TestableHttpClient.IHttpRequestMessagesCheck.WithHttpVersion(System.Version! httpVersion, int expectedNumberOfRequests) -> TestableHttpClient.IHttpRequestMessagesCheck!
TestableHttpClient.IHttpRequestMessagesCheck.WithRequestUri(string! pattern) -> TestableHttpClient.IHttpRequestMessagesCheck!
-TestableHttpClient.IHttpRequestMessagesCheck.WithRequestUri(string! pattern, int expectedNumberOfRequests) -> TestableHttpClient.IHttpRequestMessagesCheck!
\ No newline at end of file
+TestableHttpClient.IHttpRequestMessagesCheck.WithRequestUri(string! pattern, int expectedNumberOfRequests) -> TestableHttpClient.IHttpRequestMessagesCheck!
+TestableHttpClient.RequestBuilder
+TestableHttpClient.RequestBuilder.WithContent(string! pattern) -> TestableHttpClient.RequestBuilder!
+TestableHttpClient.RequestBuilder.WithHeader(string! headerName) -> TestableHttpClient.RequestBuilder!
+TestableHttpClient.RequestBuilder.WithHeader(string! headerName, string! pattern) -> TestableHttpClient.RequestBuilder!
+TestableHttpClient.RequestBuilder.WithMethod(System.Net.Http.HttpMethod! httpMethod) -> TestableHttpClient.RequestBuilder!
+TestableHttpClient.RequestBuilder.WithRequestUri(string! pattern) -> TestableHttpClient.RequestBuilder!
+TestableHttpClient.RequestBuilder.WithVersion(System.Version! httpVersion) -> TestableHttpClient.RequestBuilder!
diff --git a/src/TestableHttpClient/Request.cs b/src/TestableHttpClient/Request.cs
new file mode 100644
index 00000000..1c9d0319
--- /dev/null
+++ b/src/TestableHttpClient/Request.cs
@@ -0,0 +1,98 @@
+namespace TestableHttpClient;
+
+internal record Request : IEquatable
+{
+ public Request(UriPatternMatchingOptions uriPatternMatchingOptions)
+ {
+ UriPatternMatchingOptions = uriPatternMatchingOptions;
+ }
+
+ public UriPatternMatchingOptions UriPatternMatchingOptions { get; }
+
+ public HttpMethod? Method { get; init; }
+ public UriPattern? RequestUri { get; init; }
+ public Version? Version { get; init; }
+
+ public Dictionary? Headers { get; init; }
+
+ public string? Content { get; init; }
+
+ public Request AddHeader(string headerName) => AddHeader(headerName, Value.Any());
+
+ public Request AddHeader(string headerName, string headerValue) => AddHeader(headerName, Value.Pattern(headerValue));
+
+ public Request AddHeader(string headerName, Value headerValue)
+ {
+ if (Headers is null)
+ {
+ Dictionary headerValues = new() { [headerName] = headerValue };
+ return this with { Headers = headerValues };
+ }
+ else
+ {
+ Headers[headerName] = headerValue;
+ return this;
+ }
+ }
+
+ public bool Equals(HttpRequestMessage? other)
+ {
+ if (other is null)
+ {
+ return false;
+ }
+
+ if (Method is not null && other.Method != Method)
+ {
+ return false;
+ }
+
+ if (RequestUri is not null && other.RequestUri is not null && !RequestUri.Matches(other.RequestUri, UriPatternMatchingOptions))
+ {
+ return false;
+ }
+
+ if (Version is not null && other.Version != Version)
+ {
+ return false;
+ }
+
+ if (Headers is not null)
+ {
+ foreach (var keyValuePair in Headers)
+ {
+ if (!other.Headers.HasHeader(keyValuePair.Key, keyValuePair.Value) && (other.Content is null || !other.Content.Headers.HasHeader(keyValuePair.Key, keyValuePair.Value)))
+ {
+ return false;
+ }
+ }
+ }
+
+ if (Content is not null)
+ {
+ if (other.Content is null)
+ {
+ return false;
+ }
+
+ var stringContent = other.Content.ReadAsStringAsync()
+ .ConfigureAwait(false)
+ .GetAwaiter()
+ .GetResult();
+
+ var contentMatches = Content switch
+ {
+ "" => stringContent == Content,
+ "*" => true,
+ _ => StringMatcher.Matches(stringContent, Content),
+ };
+
+ if (!contentMatches)
+ {
+ return false;
+ }
+ }
+
+ return true;
+ }
+}
diff --git a/src/TestableHttpClient/RequestBuilder.cs b/src/TestableHttpClient/RequestBuilder.cs
new file mode 100644
index 00000000..430ee6f8
--- /dev/null
+++ b/src/TestableHttpClient/RequestBuilder.cs
@@ -0,0 +1,62 @@
+namespace TestableHttpClient;
+
+public sealed class RequestBuilder
+{
+ private Request request;
+
+ internal RequestBuilder(UriPatternMatchingOptions uriPatternMatchingOptions)
+ {
+ request = new(uriPatternMatchingOptions);
+ }
+
+ public RequestBuilder WithMethod(HttpMethod httpMethod)
+ {
+ Guard.ThrowIfNull(httpMethod);
+
+ request = request with { Method = httpMethod };
+ return this;
+ }
+
+ public RequestBuilder WithRequestUri(string pattern)
+ {
+ Guard.ThrowIfNullOrEmpty(pattern);
+
+ request = request with { RequestUri = UriPatternParser.Parse(pattern) };
+ return this;
+ }
+
+ public RequestBuilder WithVersion(Version httpVersion)
+ {
+ Guard.ThrowIfNull(httpVersion);
+
+ request = request with { Version = httpVersion };
+ return this;
+ }
+
+ public RequestBuilder WithHeader(string headerName)
+ {
+ Guard.ThrowIfNullOrEmpty(headerName);
+
+ request = request.AddHeader(headerName, Value.Any());
+ return this;
+ }
+
+ public RequestBuilder WithHeader(string headerName, string pattern)
+ {
+ Guard.ThrowIfNullOrEmpty(headerName);
+ Guard.ThrowIfNullOrEmpty(pattern);
+
+ request = request.AddHeader(headerName, Value.Pattern(pattern));
+ return this;
+ }
+
+ public RequestBuilder WithContent(string pattern)
+ {
+ Guard.ThrowIfNull(pattern);
+
+ request = request with { Content = pattern };
+ return this;
+ }
+
+ internal Request Build() => request;
+}
diff --git a/src/TestableHttpClient/Utils/MessageBuilder.cs b/src/TestableHttpClient/Utils/MessageBuilder.cs
index 888908b6..527f84c8 100644
--- a/src/TestableHttpClient/Utils/MessageBuilder.cs
+++ b/src/TestableHttpClient/Utils/MessageBuilder.cs
@@ -1,25 +1,68 @@
-namespace TestableHttpClient.Utils;
+using System.Diagnostics;
+using System.Globalization;
+using System.Net.Mime;
+
+namespace TestableHttpClient.Utils;
internal static class MessageBuilder
{
- internal static string BuildMessage(int? expectedCount, int actualCount, IEnumerable conditions)
+ internal static string BuildMessage(int? expectedCount, int actualCount, Request expectedRequest, IEnumerable conditions)
{
var pass = expectedCount switch
{
null => actualCount > 0,
_ => expectedCount == actualCount
};
+ var method = expectedRequest.Method switch
+ {
+ null => "",
+ _ => $"{expectedRequest.Method} "
+ };
var expectedMessage = expectedCount switch
{
- null => "at least one request",
- 0 => "no requests",
- 1 => "one request",
- _ => $"{expectedCount} requests"
+ null => $"at least one {method}request",
+ 0 => $"no {method}requests",
+ 1 => $"one {method}request",
+ _ => $"{expectedCount} {method}requests"
+ };
+
+ var requestUri = expectedRequest.RequestUri switch
+ {
+ null => "",
+ _ => BuildRequestUri(expectedRequest.RequestUri)
+ };
+
+ var headers = expectedRequest.Headers switch
+ {
+ null => "",
+ _ => BuildHeaders(expectedRequest.Headers)
};
+ string content = string.Empty;
+
+ if (!string.IsNullOrEmpty(expectedRequest.Content))
+ {
+ StringBuilder contentBuilder = new();
+ if (string.IsNullOrEmpty(headers))
+ {
+ contentBuilder.AppendLine(" with content:");
+ }
+ else
+ {
+ contentBuilder.AppendLine("and content:");
+ }
+
+ string[] splitcontent = expectedRequest.Content!.Split(['\n']);
+ foreach (var line in splitcontent)
+ {
+ contentBuilder.AppendLine(CultureInfo.InvariantCulture, $" {line.Trim('\r')}");
+ }
+ content = contentBuilder.ToString();
+ }
+
var expectedConditions = string.Empty;
- if (conditions.Any())
+ if (conditions is not null && conditions.Any())
{
expectedConditions = $" with {string.Join(", ", conditions)}";
}
@@ -33,8 +76,98 @@ internal static string BuildMessage(int? expectedCount, int actualCount, IEnumer
return pass switch
{
- true => $"Expected {expectedMessage} to be made{expectedConditions}, and {actualMessage}.",
- false => $"Expected {expectedMessage} to be made{expectedConditions}, but {actualMessage}."
+ true => $"Expected {expectedMessage} to be made{requestUri}{headers}{content}{expectedConditions}, and {actualMessage}.",
+ false => $"Expected {expectedMessage} to be made{requestUri}{headers}{content}{expectedConditions}, but {actualMessage}."
+ };
+ }
+
+ private static string BuildRequestUri(UriPattern requestUri)
+ {
+ string scheme = BuildValue(requestUri.Scheme);
+ string host = BuildValue(requestUri.Host);
+ string port = BuildValue(requestUri.Port);
+ string path = BuildValue(requestUri.Path);
+ string query = BuildValue(requestUri.Query);
+
+ StringBuilder uriBuilder = new();
+ if (!string.IsNullOrEmpty(scheme))
+ {
+ uriBuilder.Append(scheme).Append("://");
+ }
+
+ if (!string.IsNullOrEmpty(host))
+ {
+ uriBuilder.Append(host);
+ }
+ else if (uriBuilder.Length > 0)
+ {
+ uriBuilder.Append("");
+ }
+
+ if (!string.IsNullOrEmpty(port))
+ {
+ uriBuilder.Append(':').Append(port);
+ }
+
+ if (!string.IsNullOrEmpty(path))
+ {
+ uriBuilder.Append(path);
+ }
+
+ if (!string.IsNullOrEmpty(query))
+ {
+ uriBuilder.Append('?').Append(query);
+ }
+
+ var result = uriBuilder.ToString();
+ if (string.IsNullOrEmpty(result))
+ {
+ return result;
+ }
+ else
+ {
+ return $" to '{result}'";
+ }
+ }
+
+ private static string BuildHeaders(Dictionary headerValues)
+ {
+ StringBuilder headers = new();
+
+ foreach (var header in headerValues)
+ {
+ var value = BuildValue(header.Value);
+ if (string.IsNullOrEmpty(value))
+ {
+ headers.AppendLine(CultureInfo.InvariantCulture, $" {header.Key}: ");
+ }
+ else
+ {
+ headers.AppendLine(CultureInfo.InvariantCulture, $" {header.Key}: '{value}'");
+ }
+ }
+
+ var result = headers.ToString();
+ if (string.IsNullOrEmpty(result))
+ {
+ return result;
+ }
+ else
+ {
+ StringBuilder headervalue = new();
+ headervalue.AppendLine(" with headers:").Append(result);
+ return headervalue.ToString(); ;
+ }
+ }
+
+ private static string BuildValue(Value value)
+ {
+ return value switch
+ {
+ AnyValue => "",
+ ExactValue exactValue => exactValue.ExpectedValue,
+ PatternValue patternValue => patternValue.ExpectedValue,
+ _ => throw new UnreachableException()
};
}
}
diff --git a/src/TestableHttpClient/Utils/NetStandardPollyFill.cs b/src/TestableHttpClient/Utils/NetStandardPollyFill.cs
index f3d1acc3..d6f3626c 100644
--- a/src/TestableHttpClient/Utils/NetStandardPollyFill.cs
+++ b/src/TestableHttpClient/Utils/NetStandardPollyFill.cs
@@ -1,5 +1,7 @@
#if NETSTANDARD
+using System.Runtime.CompilerServices;
+
namespace TestableHttpClient.Utils;
internal static class NetStandardPollyFill
@@ -13,6 +15,16 @@ public static string Replace(this string input, string oldValue, string newValue
{
return input.Replace(oldValue, newValue);
}
+
+ public static StringBuilder Append(this StringBuilder builder, IFormatProvider? provider, string handler)
+ {
+ return builder.Append(handler);
+ }
+
+ public static StringBuilder AppendLine(this StringBuilder builder, IFormatProvider? provider, string handler)
+ {
+ return builder.AppendLine(handler);
+ }
}
#endif
diff --git a/src/TestableHttpClient/Utils/RequestFormatOptions.cs b/src/TestableHttpClient/Utils/RequestFormatOptions.cs
new file mode 100644
index 00000000..756710df
--- /dev/null
+++ b/src/TestableHttpClient/Utils/RequestFormatOptions.cs
@@ -0,0 +1,13 @@
+namespace TestableHttpClient.Utils;
+
+[Flags]
+internal enum RequestFormatOptions
+{
+ HttpMethod = 1,
+ RequestUri = 2,
+ HttpVersion = 4,
+ RequestLine = HttpMethod | RequestUri | HttpVersion,
+ Headers = 8,
+ Content = 16,
+ All = RequestLine | Headers | Content
+}
diff --git a/src/TestableHttpClient/Utils/UriPattern.cs b/src/TestableHttpClient/Utils/UriPattern.cs
index 3c2cc39a..f4ac5ef4 100644
--- a/src/TestableHttpClient/Utils/UriPattern.cs
+++ b/src/TestableHttpClient/Utils/UriPattern.cs
@@ -2,7 +2,7 @@
namespace TestableHttpClient.Utils;
-internal sealed class UriPattern
+internal sealed record UriPattern
{
public static UriPattern Any { get; } = new UriPattern();
diff --git a/src/TestableHttpClient/Utils/Value.cs b/src/TestableHttpClient/Utils/Value.cs
index 27569edb..bba79836 100644
--- a/src/TestableHttpClient/Utils/Value.cs
+++ b/src/TestableHttpClient/Utils/Value.cs
@@ -12,23 +12,19 @@ internal abstract record Value
}
[DebuggerDisplay("Any value")]
-file sealed record AnyValue : Value
+internal sealed record AnyValue : Value
{
internal override bool Matches(string value, bool ignoreCase) => true;
}
-[DebuggerDisplay("Exact value: {expectedValue}")]
-file sealed record ExactValue : Value
+[DebuggerDisplay("Exact value: {ExpectedValue}")]
+internal sealed record ExactValue(string ExpectedValue) : Value
{
- private readonly string expectedValue;
- public ExactValue(string expectedValue) => this.expectedValue = expectedValue;
- internal override bool Matches(string value, bool ignoreCase) => expectedValue.Equals(value, ignoreCase ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal);
+ internal override bool Matches(string value, bool ignoreCase) => ExpectedValue.Equals(value, ignoreCase ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal);
}
-[DebuggerDisplay("Pattern value: {pattern}")]
-file sealed record PatternValue : Value
+[DebuggerDisplay("Pattern value: {ExpectedValue}")]
+internal sealed record PatternValue(string ExpectedValue) : Value
{
- private readonly string pattern;
- public PatternValue(string pattern) => this.pattern = pattern;
- internal override bool Matches(string value, bool ignoreCase) => StringMatcher.Matches(value, pattern, ignoreCase);
+ internal override bool Matches(string value, bool ignoreCase) => StringMatcher.Matches(value, ExpectedValue, ignoreCase);
}
diff --git a/test/Directory.Build.targets b/test/Directory.Build.targets
index 69c311b3..b40c836a 100644
--- a/test/Directory.Build.targets
+++ b/test/Directory.Build.targets
@@ -1,5 +1,5 @@
-
+
diff --git a/test/TestableHttpClient.Tests/HttpRequestMessageAsserterTests/WithFilter.cs b/test/TestableHttpClient.Tests/HttpRequestMessageAsserterTests/WithFilter.cs
index a8c5a664..3aa86ed6 100644
--- a/test/TestableHttpClient.Tests/HttpRequestMessageAsserterTests/WithFilter.cs
+++ b/test/TestableHttpClient.Tests/HttpRequestMessageAsserterTests/WithFilter.cs
@@ -1,5 +1,6 @@
namespace TestableHttpClient.Tests.HttpRequestMessageAsserterTests;
+[Obsolete("WithFilter will be made internal, since it should no longer be necesary to use.")]
public sealed class WithFilter
{
[Fact]
diff --git a/test/TestableHttpClient.Tests/HttpRequestMessagesCheckExtensionsTests/WithFormUrlEncodedContent.cs b/test/TestableHttpClient.Tests/HttpRequestMessagesCheckExtensionsTests/WithFormUrlEncodedContent.cs
index 3d981229..279ef4d4 100644
--- a/test/TestableHttpClient.Tests/HttpRequestMessagesCheckExtensionsTests/WithFormUrlEncodedContent.cs
+++ b/test/TestableHttpClient.Tests/HttpRequestMessagesCheckExtensionsTests/WithFormUrlEncodedContent.cs
@@ -88,7 +88,12 @@ public void WithFormUrlEncodedContent_WithoutNumberOfRequests_RequestWithNotMatc
var exception = Assert.Throws(() => sut.WithFormUrlEncodedContent([new KeyValuePair("username", "alice")]));
- Assert.Equal("Expected at least one request to be made with content 'username=alice', but no requests were made.", exception.Message);
+ Assert.Equal("""
+ Expected at least one request to be made with content:
+ username=alice
+ , but no requests were made.
+ """, exception.Message);
+
}
[Fact]
@@ -103,6 +108,12 @@ public void WithFormUrlEncodedContent_WithoutNumberOfRequests_RequestWithNotMatc
var exception = Assert.Throws(() => sut.WithFormUrlEncodedContent([new KeyValuePair("username", "alice")]));
- Assert.Equal("Expected at least one request to be made with content 'username=alice', header 'Content-Type' and value 'application/x-www-form-urlencoded*', but no requests were made.", exception.Message);
+ Assert.Equal("""
+ Expected at least one request to be made with headers:
+ Content-Type: 'application/x-www-form-urlencoded*'
+ and content:
+ username=alice
+ , but no requests were made.
+ """, exception.Message);
}
}
diff --git a/test/TestableHttpClient.Tests/HttpRequestMessagesCheckExtensionsTests/WithJsonContent.cs b/test/TestableHttpClient.Tests/HttpRequestMessagesCheckExtensionsTests/WithJsonContent.cs
index 29b31c19..a6d72462 100644
--- a/test/TestableHttpClient.Tests/HttpRequestMessagesCheckExtensionsTests/WithJsonContent.cs
+++ b/test/TestableHttpClient.Tests/HttpRequestMessagesCheckExtensionsTests/WithJsonContent.cs
@@ -47,7 +47,11 @@ public void WithJsonContent_RequestWithDifferentContent_ThrowsHttpRequestMessage
HttpRequestMessageAsserter sut = new([request]);
var exception = Assert.Throws(() => sut.WithJsonContent(null));
- Assert.Equal("Expected at least one request to be made with content 'null', but no requests were made.", exception.Message);
+ Assert.Equal("""
+ Expected at least one request to be made with content:
+ null
+ , but no requests were made.
+ """, exception.Message);
}
[Fact]
@@ -60,6 +64,12 @@ public void WithJsonContent_RequestWithDifferentContentType_ThrowsHttpRequestMes
HttpRequestMessageAsserter sut = new([request]);
var exception = Assert.Throws(() => sut.WithJsonContent(null));
- Assert.Equal("Expected at least one request to be made with content 'null', header 'Content-Type' and value 'application/json*', but no requests were made.", exception.Message);
+ Assert.Equal("""
+ Expected at least one request to be made with headers:
+ Content-Type: 'application/json*'
+ and content:
+ null
+ , but no requests were made.
+ """, exception.Message);
}
}
diff --git a/test/TestableHttpClient.Tests/RequestBuilderTests.cs b/test/TestableHttpClient.Tests/RequestBuilderTests.cs
new file mode 100644
index 00000000..17b19eff
--- /dev/null
+++ b/test/TestableHttpClient.Tests/RequestBuilderTests.cs
@@ -0,0 +1,148 @@
+using TestableHttpClient.Utils;
+
+namespace TestableHttpClient.Tests;
+
+public sealed class RequestBuilderTests
+{
+ private readonly RequestBuilder sut = new(new UriPatternMatchingOptions());
+
+ [Fact]
+ public void Build_ByDefault_CreatesEmptyRequest()
+ {
+ Request request = sut.Build();
+
+ Assert.Null(request.Method);
+ Assert.Null(request.RequestUri);
+ Assert.Null(request.Version);
+ Assert.Null(request.Headers);
+ Assert.Null(request.Content);
+ }
+
+ [Fact]
+ public void WithMethod_NullMethod_ThrowsArgumentNullException()
+ {
+ Assert.Throws(() => sut.WithMethod(null!));
+ }
+
+ [Fact]
+ public void WithMethod_CreatesRequestWithMethod()
+ {
+ Request request = sut.WithMethod(HttpMethod.Post).Build();
+
+ Assert.Equal(HttpMethod.Post, request.Method);
+ Assert.Null(request.RequestUri);
+ Assert.Null(request.Version);
+ Assert.Null(request.Headers);
+ Assert.Null(request.Content);
+ }
+
+ [Fact]
+ public void WithRequestUri_NullUri_ThrowsArgumentNullException()
+ {
+ Assert.Throws(() => sut.WithRequestUri(null!));
+ }
+
+ [Fact]
+ public void WithRequestUri_EmptyUri_ThrowsArgumentException()
+ {
+ Assert.Throws(() => sut.WithRequestUri(string.Empty));
+ }
+
+ [Fact]
+ public void WithRequestUri_UriPattern_ShouldSetRequestUri()
+ {
+ Request request = sut.WithRequestUri("http*//test.example").Build();
+
+ Assert.Null(request.Method);
+ Assert.Equal(UriPatternParser.Parse("http*//test.example"), request.RequestUri);
+ Assert.Null(request.Version);
+ Assert.Null(request.Headers);
+ Assert.Null(request.Content);
+ }
+
+ [Fact]
+ public void WithVersion_NullVersion_ThrowsArgumentNullException()
+ {
+ Assert.Throws(() => sut.WithVersion(null!));
+ }
+
+ [Fact]
+ public void WithVersion_CreatesRequestWithVersion()
+ {
+ Request request = sut.WithVersion(HttpVersion.Version11).Build();
+
+ Assert.Null(request.Method);
+ Assert.Null(request.RequestUri);
+ Assert.Equal(HttpVersion.Version11, request.Version);
+ Assert.Null(request.Headers);
+ Assert.Null(request.Content);
+ }
+
+ [Fact]
+ public void WithHeader_NullHeaderName_ThrowsArgumentNullException()
+ {
+ Assert.Throws(() => sut.WithHeader(null!));
+ Assert.Throws(() => sut.WithHeader(null!, "test"));
+ }
+
+ [Fact]
+ public void WithHeader_EmptyHeaderName_ThrowsArgumentException()
+ {
+ Assert.Throws(() => sut.WithHeader(string.Empty));
+ Assert.Throws(() => sut.WithHeader(string.Empty, "test"));
+ }
+
+ [Fact]
+ public void WithHeader_ValidHeaderNameNoValue_CreatesRequestWithHeaderWithAnyValue()
+ {
+ Request request = sut.WithHeader("Content-Length").Build();
+
+ Assert.Null(request.Method);
+ Assert.Null(request.RequestUri);
+ Assert.Null(request.Version);
+ Assert.Equal(new Dictionary() { ["Content-Length"] = Value.Any() }, request.Headers);
+ Assert.Null(request.Content);
+ }
+
+ [Fact]
+ public void WithHeader_NullPattern_ThrowsArgumentNullException()
+ {
+ Assert.Throws(() => sut.WithHeader("Content-Length", null!));
+ }
+
+ [Fact]
+ public void WithHeader_EmptyPattern_ThrowsArgumentException()
+ {
+ Assert.Throws(() => sut.WithHeader("Content-Length", string.Empty));
+ }
+
+ [Fact]
+ public void WithHeader_ValidHeaderNamePatternValue_CreatesRequestWithHeaderWithAnyValue()
+ {
+ Request request = sut.WithHeader("Content-Length", "*").Build();
+
+ Assert.Null(request.Method);
+ Assert.Null(request.RequestUri);
+ Assert.Null(request.Version);
+ Assert.Equal(new Dictionary() { ["Content-Length"] = Value.Pattern("*") }, request.Headers);
+ Assert.Null(request.Content);
+ }
+
+ [Fact]
+ public void WithContent_NullContent_ThrowsArgumentNullException()
+ {
+ Assert.Throws(() => sut.WithContent(null!));
+ }
+
+ [Fact]
+ public void WithContent_CreatesRequestWithContent()
+ {
+ Request request = sut.WithContent("content").Build();
+
+ Assert.Null(request.Method);
+ Assert.Null(request.RequestUri);
+ Assert.Null(request.Version);
+ Assert.Null(request.Headers);
+ Assert.Equal("content", request.Content);
+ }
+}
diff --git a/test/TestableHttpClient.Tests/TestableHttpMessageHandlerExtensionsTests/CreateClientWithConfigurer.cs b/test/TestableHttpClient.Tests/TestableHttpMessageHandlerExtensionsTests/CreateClientWithConfigurer.cs
index b9597838..91115556 100644
--- a/test/TestableHttpClient.Tests/TestableHttpMessageHandlerExtensionsTests/CreateClientWithConfigurer.cs
+++ b/test/TestableHttpClient.Tests/TestableHttpMessageHandlerExtensionsTests/CreateClientWithConfigurer.cs
@@ -61,7 +61,7 @@ public void CreateClientWithConfigurer_WhenConfiguringBaseAddress_DoesNotOverrid
[Fact]
public void CreateClientWithConfigurer_CallsConfigureClientWithClientToReturn()
{
- HttpClient? capturedClient= null;
+ HttpClient? capturedClient = null;
using TestableHttpMessageHandler sut = new();
void configureClient(HttpClient client) => capturedClient = client;
diff --git a/test/TestableHttpClient.Tests/TestableHttpMessageHandlerExtensionsTests/CreateClientWithConfigurerAndHttpMessageHandlers.cs b/test/TestableHttpClient.Tests/TestableHttpMessageHandlerExtensionsTests/CreateClientWithConfigurerAndHttpMessageHandlers.cs
index 87cbfdf1..1ab23966 100644
--- a/test/TestableHttpClient.Tests/TestableHttpMessageHandlerExtensionsTests/CreateClientWithConfigurerAndHttpMessageHandlers.cs
+++ b/test/TestableHttpClient.Tests/TestableHttpMessageHandlerExtensionsTests/CreateClientWithConfigurerAndHttpMessageHandlers.cs
@@ -8,7 +8,7 @@ public void CreateClientWithConfigurerAndHttpMessageHandlers_NullTestableHttpMes
TestableHttpMessageHandler sut = null!;
static void configureClient(HttpClient _) { }
-
+
var exception = Assert.Throws(() => sut.CreateClient(configureClient, []));
Assert.Equal("handler", exception.ParamName);
}
@@ -18,7 +18,7 @@ public void CreateClientWithConfigurerAndHttpMessageHandlers_NullConfigureAction
{
using TestableHttpMessageHandler sut = new();
Action configureClient = null!;
-
+
var exception = Assert.Throws(() => sut.CreateClient(configureClient, []));
Assert.Equal("configureClient", exception.ParamName);
}
@@ -41,7 +41,7 @@ public void CreateClientWithConfigurerAndHttpMessageHandlers_CallsConfigureClien
HttpClient? capturedClient = null;
using TestableHttpMessageHandler sut = new();
void configureClient(HttpClient client) => capturedClient = client;
-
+
using var client = sut.CreateClient(configureClient, []);
Assert.Same(client, capturedClient);
diff --git a/test/TestableHttpClient.Tests/Utils/MessageBuilderTests.cs b/test/TestableHttpClient.Tests/Utils/MessageBuilderTests.cs
index 5f429638..e26de18b 100644
--- a/test/TestableHttpClient.Tests/Utils/MessageBuilderTests.cs
+++ b/test/TestableHttpClient.Tests/Utils/MessageBuilderTests.cs
@@ -5,9 +5,10 @@ namespace TestableHttpClient.Tests.Utils;
public class MessageBuilderTests
{
[Fact]
- public void BuildMessage_NoExpectedCountZeroActualCountNoConditions()
+ public void BuildMessage_NoExpectedCountZeroActualCountDefaultExpectedRequest()
{
- var result = MessageBuilder.BuildMessage(null, 0, []);
+ Request request = new(new());
+ var result = MessageBuilder.BuildMessage(null, 0, request, []);
Assert.Equal("Expected at least one request to be made, but no requests were made.", result);
}
@@ -16,17 +17,19 @@ public void BuildMessage_NoExpectedCountZeroActualCountNoConditions()
[InlineData(1, "one request was made")]
[InlineData(2, "2 requests were made")]
[InlineData(10, "10 requests were made")]
- public void BuildMessage_NoExpectedCountVariableActualCountNoConditions(int actualCount, string expectedMessage)
+ public void BuildMessage_NoExpectedCountVariableActualCountDefaultExpectedRequest(int actualCount, string expectedMessage)
{
- var result = MessageBuilder.BuildMessage(null, actualCount, []);
+ Request request = new(new());
+ var result = MessageBuilder.BuildMessage(null, actualCount, request, []);
Assert.Equal($"Expected at least one request to be made, and {expectedMessage}.", result);
}
[Fact]
- public void BuildMessage_ZeroExpectedCountZeroActualCountNoConditions()
+ public void BuildMessage_ZeroExpectedCountZeroActualCountDefaultExpectedRequest()
{
- var result = MessageBuilder.BuildMessage(0, 0, []);
+ Request request = new(new());
+ var result = MessageBuilder.BuildMessage(0, 0, request, []);
Assert.Equal($"Expected no requests to be made, and no requests were made.", result);
}
@@ -35,20 +38,125 @@ public void BuildMessage_ZeroExpectedCountZeroActualCountNoConditions()
[InlineData(1, "one request")]
[InlineData(2, "2 requests")]
[InlineData(10, "10 requests")]
- public void BuildMessage_VariableExpectedCountZeroActualCountNoConditions(int expectedCount, string expectedMessage)
+ public void BuildMessage_VariableExpectedCountZeroActualCountDefaultExpectedRequest(int expectedCount, string expectedMessage)
{
- var result = MessageBuilder.BuildMessage(expectedCount, 0, []);
+ Request request = new(new());
+ var result = MessageBuilder.BuildMessage(expectedCount, 0, request, []);
Assert.Equal($"Expected {expectedMessage} to be made, but no requests were made.", result);
}
+ [Theory]
+ [InlineData(null, "at least one GET request")]
+ [InlineData(1, "one GET request")]
+ [InlineData(2, "2 GET requests")]
+ [InlineData(10, "10 GET requests")]
+ public void BuildMessage_VariableExpectedCountZeroActualCountRequestSpecifyingMethod(int? expectedCount, string expectedMessage)
+ {
+ Request request = new(new())
+ {
+ Method = HttpMethod.Get,
+ };
+
+ var result = MessageBuilder.BuildMessage(expectedCount, 0, request, []);
+
+ Assert.Equal($"Expected {expectedMessage} to be made, but no requests were made.", result);
+ }
+
+ [Theory]
+ [InlineData("/test", "/test")]
+ [InlineData("https://*/test", "https:///test")]
+ [InlineData("/test?Hello", "/test?Hello")]
+ public void BuildMessage_OnexpectedCountZeroActualCountRequestSpecifyingUri(string uri, string expectedRepresentation)
+ {
+ Request request = new(new())
+ {
+ RequestUri = UriPatternParser.Parse(uri)
+ };
+
+ var result = MessageBuilder.BuildMessage(1, 0, request, []);
+
+ Assert.Equal($"Expected one request to be made to '{expectedRepresentation}', but no requests were made.", result);
+ }
+
+ [Fact]
+ public void BuildMessage_OnexpectedCountZeroActualCountRequestSpecifyingHeaders()
+ {
+ Request request = new(new())
+ {
+ Headers = new Dictionary()
+ {
+ ["Header1"] = Value.Any(),
+ ["Header2"] = Value.Pattern("Value2"),
+ ["Header3"] = Value.Exact("Value3"),
+ }
+ };
+
+ var result = MessageBuilder.BuildMessage(1, 0, request, []);
+
+ Assert.Equal("""
+ Expected one request to be made with headers:
+ Header1:
+ Header2: 'Value2'
+ Header3: 'Value3'
+ , but no requests were made.
+ """, result);
+ }
+
+ [Fact]
+ public void BuildMessage_OnexpectedCountZeroActualCountRequestSpecifyingContent()
+ {
+ Request request = new(new())
+ {
+ Content = "test"
+ };
+
+ var result = MessageBuilder.BuildMessage(1, 0, request, []);
+
+ Assert.Equal("""
+ Expected one request to be made with content:
+ test
+ , but no requests were made.
+ """, result);
+ }
+
+ [Fact]
+ public void BuildMessage_OnexpectedCountZeroActualCountRequestSpecifyingHeadersSpecifyingContent()
+ {
+ Request request = new(new())
+ {
+ Headers = new Dictionary()
+ {
+ ["Content-Type"] = Value.Exact("application/json")
+ },
+ Content = """
+ {
+ "test": "value"
+ }
+ """
+ };
+
+ var result = MessageBuilder.BuildMessage(1, 0, request, []);
+
+ Assert.Equal("""
+ Expected one request to be made with headers:
+ Content-Type: 'application/json'
+ and content:
+ {
+ "test": "value"
+ }
+ , but no requests were made.
+ """, result);
+ }
+
[Theory]
[InlineData("with condition 1", "condition 1")]
[InlineData("with condition 1, condition 2", "condition 1", "condition 2")]
[InlineData("with condition 1, condition 2, condition 3", "condition 1", "condition 2", "condition 3")]
public void BuildMessage_NullExpectedCountZeroActualCountVariableAmountOfConditions(string expectedCondition, params string[] conditions)
{
- var result = MessageBuilder.BuildMessage(null, 0, conditions);
+ Request request = new(new());
+ var result = MessageBuilder.BuildMessage(null, 0, request, conditions);
Assert.Equal($"Expected at least one request to be made {expectedCondition}, but no requests were made.", result);
}
@@ -59,7 +167,8 @@ public void BuildMessage_NullExpectedCountZeroActualCountVariableAmountOfConditi
[InlineData("with condition 1, condition 2, condition 3", "condition 1", "condition 2", "condition 3")]
public void BuildMessage_NullExpectedCountOneActualCountVariableAmountOfConditions(string expectedCondition, params string[] conditions)
{
- var result = MessageBuilder.BuildMessage(null, 1, conditions);
+ Request request = new(new());
+ var result = MessageBuilder.BuildMessage(null, 1, request, conditions);
Assert.Equal($"Expected at least one request to be made {expectedCondition}, and one request was made.", result);
}