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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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<TValue>.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.
Expand Down
2 changes: 1 addition & 1 deletion Common.targets
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<Project>
<PropertyGroup>
<Version>5.7.0</Version>
<Version>5.8.0</Version>
<LangVersion>preview</LangVersion>
<Authors>Avanade</Authors>
<Company>Avanade</Company>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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("");

}
}
}
Expand Down
11 changes: 5 additions & 6 deletions src/UnitTestEx/AspNetCore/ApiTesterBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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("");
}
}
}
Expand Down Expand Up @@ -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;
}
Expand Down
18 changes: 18 additions & 0 deletions src/UnitTestEx/Assertors/HttpResponseMessageAssertorBaseT.cs
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,12 @@ public TSelf Assert(HttpStatusCode statusCode, string? content)
/// <returns>The <see cref="HttpResponseMessageAssertorBase{TSelf}"/> instance to support fluent-style method-chaining.</returns>
public TSelf AssertContentTypeJson() => AssertContentType(MediaTypeNames.Application.Json);

/// <summary>
/// Asserts that the <see cref="HttpResponseMessageAssertorBase.Response"/> content type is '<c>application/problem+json</c>'.
/// </summary>
/// <returns>The <see cref="HttpResponseMessageAssertorBase{TSelf}"/> instance to support fluent-style method-chaining.</returns>
public TSelf AssertContentTypeProblemJson() => AssertContentType("application/problem+json");

/// <summary>
/// Asserts that the <see cref="HttpResponseMessageAssertorBase.Response"/> content type is <see cref="MediaTypeNames.Text.Plain"/>.
/// </summary>
Expand Down Expand Up @@ -187,6 +193,18 @@ public TSelf AssertETagHeader(System.Net.Http.Headers.EntityTagHeaderValue expec
/// <returns>The <see cref="HttpResponseMessageAssertorBase{TSelf}"/> instance to support fluent-style method-chaining.</returns>
public TSelf AssertForbidden() => Assert(HttpStatusCode.Forbidden);

/// <summary>
/// Asserts that the <see cref="HttpResponseMessageAssertorBase.Response"/> is a <see cref="HttpStatusCode.InternalServerError"/>.
/// </summary>
/// <returns>The <see cref="HttpResponseMessageAssertorBase{TSelf}"/> instance to support fluent-style method-chaining.</returns>
public TSelf AssertInternalServerError() => Assert(HttpStatusCode.InternalServerError);

/// <summary>
/// Asserts that the <see cref="HttpResponseMessageAssertorBase.Response"/> is a <see cref="HttpStatusCode.ServiceUnavailable"/>.
/// </summary>
/// <returns>The <see cref="HttpResponseMessageAssertorBase{TSelf}"/> instance to support fluent-style method-chaining.</returns>
public TSelf AssertServiceUnavailable() => Assert(HttpStatusCode.ServiceUnavailable);

/// <summary>
/// Asserts that the <see cref="HttpResponseMessageAssertorBase.Response"/> contains the expected error <paramref name="messages"/>.
/// </summary>
Expand Down
9 changes: 5 additions & 4 deletions src/UnitTestEx/Assertors/HttpResponseMessageAssertorT.cs
Original file line number Diff line number Diff line change
Expand Up @@ -39,13 +39,14 @@ internal HttpResponseMessageAssertor(HttpResponseMessageAssertor assertor) : thi
/// Gets the response content as the deserialized JSON value.
/// </summary>
/// <returns>The result value.</returns>
/// <remarks>The corresponding content type is not asserted, where this is required then <see cref="HttpResponseMessageAssertorBase.GetValue{T}(string?)"/> should be used.</remarks>
public TValue? Value
{
get
{
if (!_valueIsDeserialized)
{
_value = GetValue<TValue>();
_value = GetValue<TValue>(null);
_valueIsDeserialized = true;
}

Expand All @@ -60,7 +61,7 @@ public TValue? Value
/// <returns>The <see cref="HttpResponseMessageAssertor{TValue}"/> to support fluent-style method-chaining.</returns>
public HttpResponseMessageAssertor<TValue> AssertLocationHeader(Func<TValue?, string> expected)
{
Implementor.AssertAreEqual(expected?.Invoke(GetValue<TValue>()), 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;
}

Expand All @@ -71,7 +72,7 @@ public HttpResponseMessageAssertor<TValue> AssertLocationHeader(Func<TValue?, st
/// <returns>The <see cref="HttpResponseMessageAssertor{TValue}"/> to support fluent-style method-chaining.</returns>
public HttpResponseMessageAssertor<TValue> AssertLocationHeader(Func<TValue?, Uri> expectedUri)
{
Implementor.AssertAreEqual(expectedUri?.Invoke(GetValue<TValue>()), 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;
}

Expand Down Expand Up @@ -100,7 +101,7 @@ public HttpResponseMessageAssertor<TValue> AssertLocationHeaderContains(string e
/// <param name="expected">The expected string.</param>
/// <returns>The <see cref="HttpResponseMessageAssertor{TValue}"/> to support fluent-style method-chaining.</returns>
public HttpResponseMessageAssertor<TValue> AssertLocationHeaderContains(Func<TValue?, string> expected)
=> AssertLocationHeaderContains(expected?.Invoke(GetValue<TValue>())!);
=> AssertLocationHeaderContains(expected?.Invoke(Value)!);

/// <summary>
/// Asserts that the <see cref="HttpResponseMessageAssertorBase.Response"/> matches the <paramref name="expectedValue"/>.
Expand Down
4 changes: 3 additions & 1 deletion src/UnitTestEx/Generic/GenericTesterCore.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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("");
}
}
}
Expand Down
3 changes: 1 addition & 2 deletions src/UnitTestEx/Mocking/MockHttpClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -187,7 +187,7 @@ public MockHttpClient WithoutHttpMessageHandlers()
}

/// <summary>
/// Specifies that the configurations for the <see cref="GetHttpClient()"/> aer to be used.
/// Specifies that the configurations for the <see cref="GetHttpClient()"/> are to be used.
/// </summary>
/// <returns>The <see cref="MockHttpClient"/> to support fluent-style method-chaining.</returns>
/// <remarks>By default the <see cref="GetHttpClient()"/> configurations are not invoked.</remarks>
Expand Down Expand Up @@ -428,7 +428,6 @@ public void Add(MockHttpClient client)
(res ?? new()).Add(seq.Respond());
}
});

}
}

Expand Down
25 changes: 15 additions & 10 deletions src/UnitTestEx/Mocking/MockHttpClientResponse.cs
Original file line number Diff line number Diff line change
Expand Up @@ -182,32 +182,35 @@ public void With(HttpContent? content = null, HttpStatusCode? statusCode = null,
/// </summary>
/// <param name="content">The <see cref="string"/> content.</param>
/// <param name="statusCode">The optional <see cref="HttpStatusCode"/> (defaults to <see cref="HttpStatusCode.OK"/>).</param>
/// <param name="mediaType">The optional media type (defaults to <see cref="MediaTypeNames.Text.Plain"/>).</param>
/// <param name="response">The optional action to enable additional configuration of the <see cref="HttpResponseMessage"/>.</param>
public void With(string content, HttpStatusCode? statusCode = null, Action<HttpResponseMessage>? 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<HttpResponseMessage>? response = null) => With(new StringContent(content ?? throw new ArgumentNullException(nameof(content)), null, mediaType!), statusCode, response);

/// <summary>
/// Provides the mocked response using the <paramref name="value"/> which will be automatically converted to JSON content.
/// </summary>
/// <typeparam name="T">The value <see cref="Type"/>.</typeparam>
/// <param name="value">The value to convert to <see cref="MediaTypeNames.Application.Json"/> content.</param>
/// <param name="value">The value to convert to JSON content.</param>
/// <param name="statusCode">The optional <see cref="HttpStatusCode"/> (defaults to <see cref="HttpStatusCode.OK"/>).</param>
/// <param name="mediaType">The optional media type (defaults to <see cref="MediaTypeNames.Application.Json"/>).</param>
/// <param name="response">The optional action to enable additional configuration of the <see cref="HttpResponseMessage"/>.</param>
public void WithJson<T>(T value, HttpStatusCode? statusCode = null, Action<HttpResponseMessage>? response = null) => WithJson(_clientRequest.JsonSerializer.Serialize(value, JsonWriteFormat.None), statusCode, response);
public void WithJson<T>(T value, HttpStatusCode? statusCode = null, string? mediaType = MediaTypeNames.Application.Json, Action<HttpResponseMessage>? response = null) => WithJson(_clientRequest.JsonSerializer.Serialize(value, JsonWriteFormat.None), statusCode, mediaType, response);

/// <summary>
/// Provides the mocked response using the <paramref name="json"/> formatted string as the content.
/// </summary>
/// <param name="json">The <see cref="MediaTypeNames.Application.Json"/> content.</param>
/// <param name="statusCode">The optional <see cref="HttpStatusCode"/> (defaults to <see cref="HttpStatusCode.OK"/>).</param>
/// <param name="mediaType">The optional media type (defaults to <see cref="MediaTypeNames.Application.Json"/>).</param>
/// <param name="response">The optional action to enable additional configuration of the <see cref="HttpResponseMessage"/>.</param>
#if NET7_0_OR_GREATER
public void WithJson([StringSyntax(StringSyntaxAttribute.Json)] string json, HttpStatusCode? statusCode = null, Action<HttpResponseMessage>? response = null)
public void WithJson([StringSyntax(StringSyntaxAttribute.Json)] string json, HttpStatusCode? statusCode = null, string? mediaType = MediaTypeNames.Application.Json, Action<HttpResponseMessage>? response = null)
#else
public void WithJson(string json, HttpStatusCode? statusCode = null, Action<HttpResponseMessage>? response = null)
public void WithJson(string json, HttpStatusCode? statusCode = null, string? mediaType = MediaTypeNames.Application.Json, Action<HttpResponseMessage>? 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);
}

Expand All @@ -217,21 +220,23 @@ public void WithJson(string json, HttpStatusCode? statusCode = null, Action<Http
/// <typeparam name="TAssembly">The <see cref="Type"/> used to infer <see cref="Assembly"/> that contains the embedded resource.</typeparam>
/// <param name="resourceName">The embedded resource name (matches to the end of the fully qualifed resource name).</param>
/// <param name="statusCode">The optional <see cref="HttpStatusCode"/> (defaults to <see cref="HttpStatusCode.OK"/>).</param>
/// <param name="mediaType">The optional media type (defaults to <see cref="MediaTypeNames.Application.Json"/>).</param>
/// <param name="response">The optional action to enable additional configuration of the <see cref="HttpResponseMessage"/>.</param>
public void WithJsonResource<TAssembly>(string resourceName, HttpStatusCode? statusCode = null, Action<HttpResponseMessage>? response = null)
=> WithJsonResource(resourceName, statusCode, response, typeof(TAssembly).Assembly);
public void WithJsonResource<TAssembly>(string resourceName, HttpStatusCode? statusCode = null, string? mediaType = MediaTypeNames.Application.Json, Action<HttpResponseMessage>? response = null)
=> WithJsonResource(resourceName, statusCode, mediaType, response, typeof(TAssembly).Assembly);

/// <summary>
/// Provides the mocked response using the JSON formatted embedded resource string as the content.
/// </summary>
/// <param name="resourceName">The embedded resource name (matches to the end of the fully qualifed resource name).</param>
/// <param name="statusCode">The optional <see cref="HttpStatusCode"/> (defaults to <see cref="HttpStatusCode.OK"/>).</param>
/// <param name="mediaType">The optional media type (defaults to <see cref="MediaTypeNames.Application.Json"/>).</param>
/// <param name="response">The optional action to enable additional configuration of the <see cref="HttpResponseMessage"/>.</param>
/// <param name="assembly">The <see cref="Assembly"/> that contains the embedded resource; defaults to <see cref="Assembly.GetCallingAssembly"/>.</param>
public void WithJsonResource(string resourceName, HttpStatusCode? statusCode = null, Action<HttpResponseMessage>? response = null, Assembly? assembly = null)
public void WithJsonResource(string resourceName, HttpStatusCode? statusCode = null, string? mediaType = MediaTypeNames.Application.Json, Action<HttpResponseMessage>? 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);
}

/// <summary>
Expand Down
Loading