From 7a155c8caf9f4a5a82c16da5ba2a9e81a473e75a Mon Sep 17 00:00:00 2001 From: Eric Sibly Date: Thu, 4 Dec 2025 15:46:20 -0800 Subject: [PATCH] ## v5.8.0 - *Enhancement:* Extended the `MockHttpClientResponse.With*` methods to support optional _media type_ parameter to enable specification of the `Content-Type` header value. - *Enhancement:* Added `HttpResponseMessageAssertor.AssertContentTypeProblemJson` to enable asserting that the content type is `application/problem+json`. - *Fixed:* `HttpResponseMessageAssertor.Value` no longer asserts the content type as `application/json` by default as this could not be overridden; this should be asserted explicitly using `AssertContentType()`, `AssertContentTypeJson()`, `AssertContentTypeProblemJson`, etc. --- CHANGELOG.md | 5 ++++ Common.targets | 2 +- .../Azure/Functions/FunctionTesterBase.cs | 5 +++- src/UnitTestEx/AspNetCore/ApiTesterBase.cs | 11 ++++---- .../HttpResponseMessageAssertorBaseT.cs | 18 +++++++++++++ .../Assertors/HttpResponseMessageAssertorT.cs | 9 ++++--- src/UnitTestEx/Generic/GenericTesterCore.cs | 4 ++- src/UnitTestEx/Mocking/MockHttpClient.cs | 3 +-- .../Mocking/MockHttpClientResponse.cs | 25 +++++++++++-------- 9 files changed, 57 insertions(+), 25 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 162d10d..245c826 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,11 @@ Represents the **NuGet** versions. +## v5.8.0 +- *Enhancement:* Extended the `MockHttpClientResponse.With*` methods to support optional _media type_ parameter to enable specification of the `Content-Type` header value. +- *Enhancement:* Added `HttpResponseMessageAssertor.AssertContentTypeProblemJson` to enable asserting that the content type is `application/problem+json`. +- *Fixed:* `HttpResponseMessageAssertor.Value` no longer asserts the content type as `application/json` by default as this could not be overridden; this should be asserted explicitly using `AssertContentType()`, `AssertContentTypeJson()`, `AssertContentTypeProblemJson`, etc. + ## v5.7.0 - *Enhancement:* Added `.NET10.0` support to all `UnitTestEx` packages. - *Enhancement:* Added `AssertNoNamedHeader` to the `HttpResponseMessageAssertor` to enable asserting that a named header is not present in the response. diff --git a/Common.targets b/Common.targets index c84f041..ad02b2d 100644 --- a/Common.targets +++ b/Common.targets @@ -1,6 +1,6 @@  - 5.7.0 + 5.8.0 preview Avanade Avanade diff --git a/src/UnitTestEx.Azure.Functions/Azure/Functions/FunctionTesterBase.cs b/src/UnitTestEx.Azure.Functions/Azure/Functions/FunctionTesterBase.cs index da83d3a..4fec2b4 100644 --- a/src/UnitTestEx.Azure.Functions/Azure/Functions/FunctionTesterBase.cs +++ b/src/UnitTestEx.Azure.Functions/Azure/Functions/FunctionTesterBase.cs @@ -196,7 +196,10 @@ protected override void ResetHost() { _host.Dispose(); _host = null; - Implementor.WriteLine("The underlying UnitTestEx 'FunctionTester' Host has been reset."); + Implementor.WriteLine(""); + Implementor.WriteLine("** The underlying UnitTestEx 'FunctionTester' Host has been reset. **"); + Implementor.WriteLine(""); + } } } diff --git a/src/UnitTestEx/AspNetCore/ApiTesterBase.cs b/src/UnitTestEx/AspNetCore/ApiTesterBase.cs index 920b6f0..77b0b8f 100644 --- a/src/UnitTestEx/AspNetCore/ApiTesterBase.cs +++ b/src/UnitTestEx/AspNetCore/ApiTesterBase.cs @@ -100,7 +100,9 @@ protected override void ResetHost() { _waf.Dispose(); _waf = null; - Implementor.WriteLine("The underlying UnitTestEx 'ApiTester' Host has been reset."); + Implementor.WriteLine(""); + Implementor.WriteLine("** The underlying UnitTestEx 'ApiTester' Host has been reset. **"); + Implementor.WriteLine(""); } } } @@ -180,11 +182,8 @@ protected virtual void Dispose(bool disposing) if (_disposed) return; - if (_waf != null) - { - _waf.Dispose(); - _waf = null; - } + _waf?.Dispose(); + _waf = null; _disposed = true; } diff --git a/src/UnitTestEx/Assertors/HttpResponseMessageAssertorBaseT.cs b/src/UnitTestEx/Assertors/HttpResponseMessageAssertorBaseT.cs index 593ec63..e4af85a 100644 --- a/src/UnitTestEx/Assertors/HttpResponseMessageAssertorBaseT.cs +++ b/src/UnitTestEx/Assertors/HttpResponseMessageAssertorBaseT.cs @@ -63,6 +63,12 @@ public TSelf Assert(HttpStatusCode statusCode, string? content) /// The instance to support fluent-style method-chaining. public TSelf AssertContentTypeJson() => AssertContentType(MediaTypeNames.Application.Json); + /// + /// Asserts that the content type is 'application/problem+json'. + /// + /// The instance to support fluent-style method-chaining. + public TSelf AssertContentTypeProblemJson() => AssertContentType("application/problem+json"); + /// /// Asserts that the content type is . /// @@ -187,6 +193,18 @@ public TSelf AssertETagHeader(System.Net.Http.Headers.EntityTagHeaderValue expec /// The instance to support fluent-style method-chaining. public TSelf AssertForbidden() => Assert(HttpStatusCode.Forbidden); + /// + /// Asserts that the is a . + /// + /// The instance to support fluent-style method-chaining. + public TSelf AssertInternalServerError() => Assert(HttpStatusCode.InternalServerError); + + /// + /// Asserts that the is a . + /// + /// The instance to support fluent-style method-chaining. + public TSelf AssertServiceUnavailable() => Assert(HttpStatusCode.ServiceUnavailable); + /// /// Asserts that the contains the expected error . /// diff --git a/src/UnitTestEx/Assertors/HttpResponseMessageAssertorT.cs b/src/UnitTestEx/Assertors/HttpResponseMessageAssertorT.cs index cbe5604..cbb4e26 100644 --- a/src/UnitTestEx/Assertors/HttpResponseMessageAssertorT.cs +++ b/src/UnitTestEx/Assertors/HttpResponseMessageAssertorT.cs @@ -39,13 +39,14 @@ internal HttpResponseMessageAssertor(HttpResponseMessageAssertor assertor) : thi /// Gets the response content as the deserialized JSON value. /// /// The result value. + /// The corresponding content type is not asserted, where this is required then should be used. public TValue? Value { get { if (!_valueIsDeserialized) { - _value = GetValue(); + _value = GetValue(null); _valueIsDeserialized = true; } @@ -60,7 +61,7 @@ public TValue? Value /// The to support fluent-style method-chaining. public HttpResponseMessageAssertor AssertLocationHeader(Func expected) { - Implementor.AssertAreEqual(expected?.Invoke(GetValue()), Response.Headers?.Location?.ToString(), $"Expected and Actual HTTP Response Header '{HeaderNames.Location}' values are not equal."); + Implementor.AssertAreEqual(expected?.Invoke(Value), Response.Headers?.Location?.ToString(), $"Expected and Actual HTTP Response Header '{HeaderNames.Location}' values are not equal."); return this; } @@ -71,7 +72,7 @@ public HttpResponseMessageAssertor AssertLocationHeader(FuncThe to support fluent-style method-chaining. public HttpResponseMessageAssertor AssertLocationHeader(Func expectedUri) { - Implementor.AssertAreEqual(expectedUri?.Invoke(GetValue()), Response.Headers?.Location, $"Expected and Actual HTTP Response Header '{HeaderNames.Location}' values are not equal."); + Implementor.AssertAreEqual(expectedUri?.Invoke(Value), Response.Headers?.Location, $"Expected and Actual HTTP Response Header '{HeaderNames.Location}' values are not equal."); return this; } @@ -100,7 +101,7 @@ public HttpResponseMessageAssertor AssertLocationHeaderContains(string e /// The expected string. /// The to support fluent-style method-chaining. public HttpResponseMessageAssertor AssertLocationHeaderContains(Func expected) - => AssertLocationHeaderContains(expected?.Invoke(GetValue())!); + => AssertLocationHeaderContains(expected?.Invoke(Value)!); /// /// Asserts that the matches the . diff --git a/src/UnitTestEx/Generic/GenericTesterCore.cs b/src/UnitTestEx/Generic/GenericTesterCore.cs index ba87fa3..24fa4fe 100644 --- a/src/UnitTestEx/Generic/GenericTesterCore.cs +++ b/src/UnitTestEx/Generic/GenericTesterCore.cs @@ -155,7 +155,9 @@ protected override void ResetHost() { _host.Dispose(); _host = null; - Implementor.WriteLine("The underlying UnitTestEx 'GenericTester' Host has been reset."); + Implementor.WriteLine(""); + Implementor.WriteLine("** The underlying UnitTestEx 'GenericTester' Host has been reset. **"); + Implementor.WriteLine(""); } } } diff --git a/src/UnitTestEx/Mocking/MockHttpClient.cs b/src/UnitTestEx/Mocking/MockHttpClient.cs index f7dbe64..32987b6 100644 --- a/src/UnitTestEx/Mocking/MockHttpClient.cs +++ b/src/UnitTestEx/Mocking/MockHttpClient.cs @@ -187,7 +187,7 @@ public MockHttpClient WithoutHttpMessageHandlers() } /// - /// Specifies that the configurations for the aer to be used. + /// Specifies that the configurations for the are to be used. /// /// The to support fluent-style method-chaining. /// By default the configurations are not invoked. @@ -428,7 +428,6 @@ public void Add(MockHttpClient client) (res ?? new()).Add(seq.Respond()); } }); - } } diff --git a/src/UnitTestEx/Mocking/MockHttpClientResponse.cs b/src/UnitTestEx/Mocking/MockHttpClientResponse.cs index 7579671..c0bb1dc 100644 --- a/src/UnitTestEx/Mocking/MockHttpClientResponse.cs +++ b/src/UnitTestEx/Mocking/MockHttpClientResponse.cs @@ -182,32 +182,35 @@ public void With(HttpContent? content = null, HttpStatusCode? statusCode = null, /// /// The content. /// The optional (defaults to ). + /// The optional media type (defaults to ). /// The optional action to enable additional configuration of the . - public void With(string content, HttpStatusCode? statusCode = null, Action? response = null) => With(new StringContent(content ?? throw new ArgumentNullException(nameof(content))), statusCode, response); + public void With(string content, HttpStatusCode? statusCode = null, string? mediaType = MediaTypeNames.Text.Plain, Action? response = null) => With(new StringContent(content ?? throw new ArgumentNullException(nameof(content)), null, mediaType!), statusCode, response); /// /// Provides the mocked response using the which will be automatically converted to JSON content. /// /// The value . - /// The value to convert to content. + /// The value to convert to JSON content. /// The optional (defaults to ). + /// The optional media type (defaults to ). /// The optional action to enable additional configuration of the . - public void WithJson(T value, HttpStatusCode? statusCode = null, Action? response = null) => WithJson(_clientRequest.JsonSerializer.Serialize(value, JsonWriteFormat.None), statusCode, response); + public void WithJson(T value, HttpStatusCode? statusCode = null, string? mediaType = MediaTypeNames.Application.Json, Action? response = null) => WithJson(_clientRequest.JsonSerializer.Serialize(value, JsonWriteFormat.None), statusCode, mediaType, response); /// /// Provides the mocked response using the formatted string as the content. /// /// The content. /// The optional (defaults to ). + /// The optional media type (defaults to ). /// The optional action to enable additional configuration of the . #if NET7_0_OR_GREATER - public void WithJson([StringSyntax(StringSyntaxAttribute.Json)] string json, HttpStatusCode? statusCode = null, Action? response = null) + public void WithJson([StringSyntax(StringSyntaxAttribute.Json)] string json, HttpStatusCode? statusCode = null, string? mediaType = MediaTypeNames.Application.Json, Action? response = null) #else - public void WithJson(string json, HttpStatusCode? statusCode = null, Action? response = null) + public void WithJson(string json, HttpStatusCode? statusCode = null, string? mediaType = MediaTypeNames.Application.Json, Action? response = null) #endif { var content = new StringContent(json ?? throw new ArgumentNullException(nameof(json))); - content.Headers.ContentType = MediaTypeHeaderValue.Parse(MediaTypeNames.Application.Json); + content.Headers.ContentType = MediaTypeHeaderValue.Parse(mediaType ?? MediaTypeNames.Application.Json); With(content, statusCode, response); } @@ -217,21 +220,23 @@ public void WithJson(string json, HttpStatusCode? statusCode = null, ActionThe used to infer that contains the embedded resource. /// The embedded resource name (matches to the end of the fully qualifed resource name). /// The optional (defaults to ). + /// The optional media type (defaults to ). /// The optional action to enable additional configuration of the . - public void WithJsonResource(string resourceName, HttpStatusCode? statusCode = null, Action? response = null) - => WithJsonResource(resourceName, statusCode, response, typeof(TAssembly).Assembly); + public void WithJsonResource(string resourceName, HttpStatusCode? statusCode = null, string? mediaType = MediaTypeNames.Application.Json, Action? response = null) + => WithJsonResource(resourceName, statusCode, mediaType, response, typeof(TAssembly).Assembly); /// /// Provides the mocked response using the JSON formatted embedded resource string as the content. /// /// The embedded resource name (matches to the end of the fully qualifed resource name). /// The optional (defaults to ). + /// The optional media type (defaults to ). /// The optional action to enable additional configuration of the . /// The that contains the embedded resource; defaults to . - public void WithJsonResource(string resourceName, HttpStatusCode? statusCode = null, Action? response = null, Assembly? assembly = null) + public void WithJsonResource(string resourceName, HttpStatusCode? statusCode = null, string? mediaType = MediaTypeNames.Application.Json, Action? response = null, Assembly? assembly = null) { using var sr = Resource.GetStream(resourceName, assembly ?? Assembly.GetCallingAssembly()); - WithJson(sr.ReadToEnd(), statusCode, response); + WithJson(sr.ReadToEnd(), statusCode, mediaType, response); } ///