Skip to content

Commit c3bcb0a

Browse files
committed
Reduce differences between .netstandard and .net by adding more pollyfills.
Reduce netframework specific tests, since they are no longer needed because of the cloning.
1 parent c084e83 commit c3bcb0a

File tree

10 files changed

+38
-64
lines changed

10 files changed

+38
-64
lines changed

CHANGELOG.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,8 @@ this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
1212
- Support for .NET 9.0
1313
- Support for .NET 10.0
1414
### Changed
15-
- The TestableHttpMessageHandler now makes a clone of the original request, so that the original request can be disposed.
15+
- The TestableHttpMessageHandler now makes a clone of the original request, so that the original request can be disposed.
16+
This change also makes it possible to assert the content on .NET Framework.
1617

1718
## [0.11] - 2024-06-15
1819
### Removed

src/TestableHttpClient/HttpRequestMessagesCheckExtensions.cs

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -258,7 +258,6 @@ private static IHttpRequestMessagesCheck WithHeader(this IHttpRequestMessagesChe
258258
/// <param name="check">The implementation that hold all the request messages.</param>
259259
/// <param name="pattern">The expected content, supports wildcards.</param>
260260
/// <returns>The <seealso cref="IHttpRequestMessagesCheck"/> for further assertions.</returns>
261-
/// <remarks>Note that on .NET Framework, the HttpClient might dispose the content after sending the request.</remarks>
262261
public static IHttpRequestMessagesCheck WithContent(this IHttpRequestMessagesCheck check, string pattern) => WithContent(check, pattern, null);
263262

264263
/// <summary>
@@ -268,7 +267,6 @@ private static IHttpRequestMessagesCheck WithHeader(this IHttpRequestMessagesChe
268267
/// <param name="pattern">The expected content, supports wildcards.</param>
269268
/// <param name="expectedNumberOfRequests">The expected number of requests.</param>
270269
/// <returns>The <seealso cref="IHttpRequestMessagesCheck"/> for further assertions.</returns>
271-
/// <remarks>Note that on .NET Framework, the HttpClient might dispose the content after sending the request.</remarks>
272270
public static IHttpRequestMessagesCheck WithContent(this IHttpRequestMessagesCheck check, string pattern, int expectedNumberOfRequests) => WithContent(check, pattern, (int?)expectedNumberOfRequests);
273271

274272
private static IHttpRequestMessagesCheck WithContent(this IHttpRequestMessagesCheck check, string pattern, int? expectedNumberOfRequests)
@@ -285,7 +283,6 @@ private static IHttpRequestMessagesCheck WithContent(this IHttpRequestMessagesCh
285283
/// <param name="check">The implementation that hold all the request messages.</param>
286284
/// <param name="jsonObject">The object representation of the expected request content.</param>
287285
/// <returns>The <seealso cref="IHttpRequestMessagesCheck"/> for further assertions.</returns>
288-
/// <remarks>Note that on .NET Framework, the HttpClient might dispose the content after sending the request.</remarks>
289286
public static IHttpRequestMessagesCheck WithJsonContent(this IHttpRequestMessagesCheck check, object? jsonObject) => WithJsonContent(check, jsonObject, null, null);
290287

291288
/// <summary>
@@ -295,7 +292,6 @@ private static IHttpRequestMessagesCheck WithContent(this IHttpRequestMessagesCh
295292
/// <param name="jsonObject">The object representation of the expected request content.</param>
296293
/// <param name="jsonSerializerOptions">The serializer options that should be used for serializing te content.</param>
297294
/// <returns>The <seealso cref="IHttpRequestMessagesCheck"/> for further assertions.</returns>
298-
/// <remarks>Note that on .NET Framework, the HttpClient might dispose the content after sending the request.</remarks>
299295
public static IHttpRequestMessagesCheck WithJsonContent(this IHttpRequestMessagesCheck check, object? jsonObject, JsonSerializerOptions jsonSerializerOptions) => WithJsonContent(check, jsonObject, jsonSerializerOptions, null);
300296

301297
/// <summary>
@@ -305,7 +301,6 @@ private static IHttpRequestMessagesCheck WithContent(this IHttpRequestMessagesCh
305301
/// <param name="jsonObject">The object representation of the expected request content.</param>
306302
/// <param name="expectedNumberOfRequests">The expected number of requests.</param>
307303
/// <returns>The <seealso cref="IHttpRequestMessagesCheck"/> for further assertions.</returns>
308-
/// <remarks>Note that on .NET Framework, the HttpClient might dispose the content after sending the request.</remarks>
309304
public static IHttpRequestMessagesCheck WithJsonContent(this IHttpRequestMessagesCheck check, object? jsonObject, int expectedNumberOfRequests) => WithJsonContent(check, jsonObject, null, (int?)expectedNumberOfRequests);
310305

311306
/// <summary>
@@ -316,7 +311,6 @@ private static IHttpRequestMessagesCheck WithContent(this IHttpRequestMessagesCh
316311
/// <param name="jsonSerializerOptions">The serializer options that should be used for serializing the content.</param>
317312
/// <param name="expectedNumberOfRequests">The expected number of requests.</param>
318313
/// <returns>The <seealso cref="IHttpRequestMessagesCheck"/> for further assertions.</returns>
319-
/// <remarks>Note that on .NET Framework, the HttpClient might dispose the content after sending the request.</remarks>
320314
public static IHttpRequestMessagesCheck WithJsonContent(this IHttpRequestMessagesCheck check, object? jsonObject, JsonSerializerOptions jsonSerializerOptions, int expectedNumberOfRequests) => WithJsonContent(check, jsonObject, jsonSerializerOptions, (int?)expectedNumberOfRequests);
321315

322316
private static IHttpRequestMessagesCheck WithJsonContent(this IHttpRequestMessagesCheck check, object? jsonObject, JsonSerializerOptions? jsonSerializerOptions, int? expectedNumberOfRequests)
@@ -334,7 +328,6 @@ private static IHttpRequestMessagesCheck WithJsonContent(this IHttpRequestMessag
334328
/// <param name="check">The implementation that hold all the request messages.</param>
335329
/// <param name="nameValueCollection">The collection of key/value pairs that should be url encoded.</param>
336330
/// <returns>The <seealso cref="IHttpRequestMessagesCheck"/> for further assertions.</returns>
337-
/// <remarks>Note that on .NET Framework, the HttpClient might dispose the content after sending the request.</remarks>
338331
public static IHttpRequestMessagesCheck WithFormUrlEncodedContent(this IHttpRequestMessagesCheck check, IEnumerable<KeyValuePair<string?, string?>> nameValueCollection) => WithFormUrlEncodedContent(check, nameValueCollection, null);
339332

340333
/// <summary>
@@ -344,7 +337,6 @@ private static IHttpRequestMessagesCheck WithJsonContent(this IHttpRequestMessag
344337
/// <param name="nameValueCollection">The collection of key/value pairs that should be url encoded.</param>
345338
/// <param name="expectedNumberOfRequests">The expected number of requests.</param>
346339
/// <returns>The <seealso cref="IHttpRequestMessagesCheck"/> for further assertions.</returns>
347-
/// <remarks>Note that on .NET Framework, the HttpClient might dispose the content after sending the request.</remarks>
348340
public static IHttpRequestMessagesCheck WithFormUrlEncodedContent(this IHttpRequestMessagesCheck check, IEnumerable<KeyValuePair<string?, string?>> nameValueCollection, int expectedNumberOfRequests) => WithFormUrlEncodedContent(check, nameValueCollection, (int?)expectedNumberOfRequests);
349341

350342
private static IHttpRequestMessagesCheck WithFormUrlEncodedContent(this IHttpRequestMessagesCheck check, IEnumerable<KeyValuePair<string?, string?>> nameValueCollection, int? expectedNumberOfRequests)

src/TestableHttpClient/TestableHttpMessageHandler.cs

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -26,9 +26,7 @@ protected override void Dispose(bool disposing)
2626
[System.Diagnostics.CodeAnalysis.SuppressMessage("Reliability", "CA2000:Dispose objects before losing scope", Justification = "It gets disposed in the dispose method")]
2727
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
2828
{
29-
#if NET8_0_OR_GREATER
30-
ArgumentNullException.ThrowIfNull(request);
31-
#endif
29+
Guard.ThrowIfNull(request);
3230

3331
httpRequestMessages.Enqueue(await HttpRequestMessageCloner.ClonaAsync(request, cancellationToken).ConfigureAwait(false));
3432

@@ -38,9 +36,8 @@ protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage
3836

3937
responseMessage.RequestMessage ??= request;
4038

41-
#if !NET6_0_OR_GREATER
39+
// In .NET Standard, a response message can be null, but we need it to be at least empty like in newe version.
4240
responseMessage.Content ??= new StringContent("");
43-
#endif
4441

4542
return responseMessage;
4643
}

src/TestableHttpClient/Utils/Guard.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ internal static void ThrowIfNullOrEmpty([NotNull] string? argument, [CallerArgum
2929
throw new ArgumentException("String should not be empty", paramName);
3030
}
3131
#else
32-
ArgumentNullException.ThrowIfNullOrEmpty(argument, paramName);
32+
ArgumentException.ThrowIfNullOrEmpty(argument, paramName);
3333
#endif
3434
}
3535
}

src/TestableHttpClient/Utils/HttpRequestMessageCloner.cs

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,4 @@
1-
using System.Diagnostics.CodeAnalysis;
2-
3-
namespace TestableHttpClient.Utils;
1+
namespace TestableHttpClient.Utils;
42

53
internal static class HttpRequestMessageCloner
64
{
@@ -18,12 +16,13 @@ internal static async Task<HttpRequestMessage> ClonaAsync(HttpRequestMessage req
1816
clone.Headers.TryAddWithoutValidation(item.Key, item.Value);
1917
}
2018

21-
// Copy content (buffered)
2219
if (request.Content is not null)
2320
{
24-
var bytes = await request.Content.ReadAsByteArrayAsync().ConfigureAwait(false);
21+
var bytes = await request.Content
22+
.ReadAsByteArrayAsync(cancellationToken)
23+
.ConfigureAwait(false);
2524
var contentClone = new ByteArrayContent(bytes);
26-
25+
contentClone.Headers.Clear();
2726
// copy content headers
2827
foreach (var header in request.Content.Headers)
2928
{
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
#if NETSTANDARD
2+
3+
namespace TestableHttpClient.Utils;
4+
5+
6+
internal static class NetStandardPollyFill
7+
{
8+
public static Task<byte[]> ReadAsByteArrayAsync(this HttpContent content, CancellationToken cancellationToken = default)
9+
{
10+
return content.ReadAsByteArrayAsync();
11+
}
12+
13+
public static string Replace(this string input, string oldValue, string newValue, StringComparison comparisonType)
14+
{
15+
return input.Replace(oldValue, newValue);
16+
}
17+
}
18+
19+
#endif

src/TestableHttpClient/Utils/StringMatcher.cs

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,8 @@ internal static class StringMatcher
77
internal static bool Matches(string value, string pattern, bool ignoreCase = false)
88
{
99
var escapedPattern = Regex.Escape(pattern);
10-
#if NETSTANDARD2_0
11-
var regex = escapedPattern.Replace("\\*", "(.*)");
12-
#else
10+
1311
var regex = escapedPattern.Replace("\\*", "(.*)", StringComparison.InvariantCultureIgnoreCase);
14-
#endif
1512
RegexOptions options = ignoreCase ? RegexOptions.IgnoreCase : RegexOptions.None;
1613
return Regex.IsMatch(value, $"^{regex}$", options);
1714
}

test/TestableHttpClient.IntegrationTests/AssertingRequests.cs

Lines changed: 0 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -162,17 +162,12 @@ public async Task AssertingContent()
162162
using StringContent content = new("my special content");
163163
_ = await client.PostAsync("https://httpbin.org/post", content, TestContext.Current.CancellationToken);
164164

165-
#if NETFRAMEWORK
166-
// On .NET Framework the HttpClient disposes the content automatically. So we can't perform the same test.
167-
testHandler.ShouldHaveMadeRequests();
168-
#else
169165
testHandler.ShouldHaveMadeRequests().WithContent("my special content");
170166
testHandler.ShouldHaveMadeRequests().WithContent("my*content");
171167
testHandler.ShouldHaveMadeRequests().WithContent("*");
172168

173169
Assert.Throws<HttpRequestMessageAssertionException>(() => testHandler.ShouldHaveMadeRequests().WithContent(""));
174170
Assert.Throws<HttpRequestMessageAssertionException>(() => testHandler.ShouldHaveMadeRequests().WithContent("my"));
175-
#endif
176171
}
177172

178173
[Fact]
@@ -186,17 +181,12 @@ public async Task AssertingContent_WhenOriginalContentIsDisposed()
186181
_ = await client.PostAsync("https://httpbin.org/post", content, TestContext.Current.CancellationToken);
187182
}
188183

189-
#if NETFRAMEWORK
190-
// On .NET Framework the HttpClient disposes the content automatically. So we can't perform the same test.
191-
testHandler.ShouldHaveMadeRequests();
192-
#else
193184
testHandler.ShouldHaveMadeRequests().WithContent("my special content");
194185
testHandler.ShouldHaveMadeRequests().WithContent("my*content");
195186
testHandler.ShouldHaveMadeRequests().WithContent("*");
196187

197188
Assert.Throws<HttpRequestMessageAssertionException>(() => testHandler.ShouldHaveMadeRequests().WithContent(""));
198189
Assert.Throws<HttpRequestMessageAssertionException>(() => testHandler.ShouldHaveMadeRequests().WithContent("my"));
199-
#endif
200190
}
201191

202192
[Fact]
@@ -208,12 +198,7 @@ public async Task AssertJsonContent()
208198
using StringContent content = new("{}", Encoding.UTF8, "application/json");
209199
_ = await client.PostAsync("https://httpbin.org/post", content, TestContext.Current.CancellationToken);
210200

211-
#if NETFRAMEWORK
212-
// On .NET Framework the HttpClient disposes the content automatically. So we can't perform the same test.
213-
testHandler.ShouldHaveMadeRequests();
214-
#else
215201
testHandler.ShouldHaveMadeRequests().WithJsonContent(new { });
216-
#endif
217202
}
218203

219204
[Fact]

test/TestableHttpClient.IntegrationTests/CustomizeJsonSerialization.cs

Lines changed: 0 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,7 @@ public async Task ByDefault_CamelCasing_is_used_for_json_serialization()
1414
sut.RespondWith(Json(new { Name = "Charlie" }));
1515
using HttpClient client = sut.CreateClient();
1616

17-
#if NETFRAMEWORK
18-
string json = await client.GetStringAsync("http://localhost/myjson");
19-
#else
2017
string json = await client.GetStringAsync("http://localhost/myjson", TestContext.Current.CancellationToken);
21-
#endif
2218

2319
Assert.Equal("{\"name\":\"Charlie\"}", json);
2420
}
@@ -31,11 +27,7 @@ public async Task But_this_can_be_changed()
3127
sut.RespondWith(Json(new { Name = "Charlie" }));
3228
using HttpClient client = sut.CreateClient();
3329

34-
#if NETFRAMEWORK
35-
string json = await client.GetStringAsync("http://localhost/myjson");
36-
#else
3730
string json = await client.GetStringAsync("http://localhost/myjson", TestContext.Current.CancellationToken);
38-
#endif
3931

4032
Assert.Equal("{\"Name\":\"Charlie\"}", json);
4133
}
@@ -47,11 +39,7 @@ public async Task But_Also_directly_on_the_response()
4739
sut.RespondWith(Json(new { Name = "Charlie" }, jsonSerializerOptions: new JsonSerializerOptions()));
4840
using HttpClient client = sut.CreateClient();
4941

50-
#if NETFRAMEWORK
51-
string json = await client.GetStringAsync("http://localhost/myjson");
52-
#else
5342
string json = await client.GetStringAsync("http://localhost/myjson", TestContext.Current.CancellationToken);
54-
#endif
5543

5644
Assert.Equal("{\"Name\":\"Charlie\"}", json);
5745
}
@@ -63,12 +51,7 @@ public async Task Asserting_also_works_this_way()
6351
using HttpClient client = sut.CreateClient();
6452
await client.PostAsJsonAsync("http://localhost", new { Name = "Charlie" }, cancellationToken: TestContext.Current.CancellationToken);
6553

66-
#if NETFRAMEWORK
67-
// Well this doesn't really work on .NET Framework.
68-
sut.ShouldHaveMadeRequests();
69-
#else
7054
sut.ShouldHaveMadeRequests().WithJsonContent(new { Name = "Charlie" });
71-
#endif
7255
}
7356

7457
[Fact]
@@ -84,11 +67,6 @@ public async Task And_we_can_go_crazy_with_it()
8467

8568
await client.PostAsJsonAsync("http://localhost", new { Name = "Charlie" }, options, cancellationToken: TestContext.Current.CancellationToken);
8669

87-
#if NETFRAMEWORK
88-
// Well this doesn't really work on .NET Framework.
89-
sut.ShouldHaveMadeRequests();
90-
#else
9170
sut.ShouldHaveMadeRequests().WithJsonContent(new { Name = "Charlie" }, options);
92-
#endif
9371
}
9472
}
Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,21 @@
1-
using System.Threading;
1+
#if NETFRAMEWORK
2+
3+
using System.Threading;
24

35
namespace TestableHttpClient.IntegrationTests;
46

5-
#if NETFRAMEWORK
67

78
internal static class NetFrameworkPollyFill
89
{
910
public static Task<string> ReadAsStringAsync(this HttpContent content, CancellationToken cancellationToken = default)
1011
{
1112
return content.ReadAsStringAsync();
1213
}
14+
15+
public static Task<string> GetStringAsync(this HttpClient client, string requestUri, CancellationToken cancellationToken = default)
16+
{
17+
return client.GetStringAsync(requestUri);
18+
}
1319
}
1420

1521
#endif

0 commit comments

Comments
 (0)