Skip to content

Commit

Permalink
V5.3.0 (#82)
Browse files Browse the repository at this point in the history
* v5.3.0
- *Enhancement:* Added `MockHttpClientResponse.Header` methods to enable the specification of headers to be included in the mocked response.
  - The `MockHttpClient.WithRequestsFromResource` YAML/JSON updated to also support the specification of response headers.

* Readme tweak.

* Fix failing test.
  • Loading branch information
chullybun authored Jan 30, 2025
1 parent cb08676 commit c6bda58
Show file tree
Hide file tree
Showing 9 changed files with 94 additions and 5 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@

Represents the **NuGet** versions.

## v5.3.0
- *Enhancement:* Added `MockHttpClientResponse.Header` methods to enable the specification of headers to be included in the mocked response.
- The `MockHttpClient.WithRequestsFromResource` YAML/JSON updated to also support the specification of response headers.

## v5.2.0
- *Enhancement:* Added `TesterBase<TSelf>.UseAdditionalConfiguration` method to enable additional configuration to be specified that overrides the `IHostBuilder` as the host is being built. This leverages the underlying `IConfigurationBuilder.AddInMemoryCollection` capability to add. This is intended to support additional configuration that is not part of the standard `appsettings.json` or `appsettings.unittest.json` configuration.

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.2.0</Version>
<Version>5.3.0</Version>
<LangVersion>preview</LangVersion>
<Authors>Avanade</Authors>
<Company>Avanade</Company>
Expand Down
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -256,6 +256,8 @@ The following represents a YAML example for one-to-one request/responses:
- method: get
uri: people/123
response:
headers:
ETag: Abc123
body: |
{
"first":"Bob",
Expand Down
9 changes: 9 additions & 0 deletions src/UnitTestEx/Mocking/MockHttpClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -440,12 +440,21 @@ private class MockConfigResponse
public HttpStatusCode? Status { get; set; }
public string? Body { get; set; }
public string? Media { get; set; }
public Dictionary<string, string?[]>? Headers { get; set; }

/// <summary>
/// Adds the response.
/// </summary>
public void Add(MockHttpClientResponse res)
{
if (Headers is not null)
{
foreach (var header in Headers)
{
res.Header(header.Key, header.Value);
}
}

if (string.IsNullOrEmpty(Body))
{
res.With(Status ?? HttpStatusCode.NoContent);
Expand Down
8 changes: 8 additions & 0 deletions src/UnitTestEx/Mocking/MockHttpClientRequest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,14 @@ private static async Task<HttpResponseMessage> CreateResponseAsync(HttpRequestMe
if (response.Content != null)
httpResponse.Content = response.Content;

if (!response.HttpHeaders.IsEmpty)
{
foreach (var header in response.HttpHeaders)
{
httpResponse.Headers.Add(header.Key, header.Value);
}
}

await response.ExecuteDelayAsync(ct).ConfigureAwait(false);

response.ResponseAction?.Invoke(httpResponse);
Expand Down
43 changes: 43 additions & 0 deletions src/UnitTestEx/Mocking/MockHttpClientResponse.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
// Copyright (c) Avanade. Licensed under the MIT License. See https://github.com/Avanade/UnitTestEx

using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Net;
using System.Net.Http;
Expand Down Expand Up @@ -59,6 +61,11 @@ internal MockHttpClientResponse(MockHttpClientRequest clientRequest, MockHttpCli
/// </summary>
internal int Count { get; set; }

/// <summary>
/// Gets the response headers.
/// </summary>
internal ConcurrentDictionary<string, List<string?>> HttpHeaders { get; } = [];

/// <summary>
/// Sets the simulated delay (sleep) for the response.
/// </summary>
Expand Down Expand Up @@ -111,6 +118,42 @@ public MockHttpClientResponse Delay(TimeSpan from, TimeSpan to)
/// <remarks>Each time a <c>Delay</c> is invoked it will override the previously set value.</remarks>
public MockHttpClientResponse Delay(int from, int to) => Delay(TimeSpan.FromMilliseconds(from), TimeSpan.FromMilliseconds(to));

/// <summary>
/// Adds the specified headers and values for the response.
/// </summary>
/// <param name="headers">The headers collection.</param>
/// <returns>The <see cref="MockHttpClientResponse"/> to support fluent-style method-chaining.</returns>
public MockHttpClientResponse Headers(IEnumerable<KeyValuePair<string, string?>> headers)
{
foreach (var header in headers)
{
Header(header.Key, header.Value);
}

return this;
}

/// <summary>
/// Adds the specified header and its value for the response.
/// </summary>
/// <param name="name">The header name.</param>
/// <param name="value">The header value.</param>
/// <returns>The <see cref="MockHttpClientResponse"/> to support fluent-style method-chaining.</returns>
public MockHttpClientResponse Header(string name, string? value) => Header(name, [value]);

/// <summary>
/// Adds the specified header and its values for the response.
/// </summary>
/// <param name="name">The header name.</param>
/// <param name="values">The header values.</param>
/// <returns>The <see cref="MockHttpClientResponse"/> to support fluent-style method-chaining.</returns>
public MockHttpClientResponse Header(string name, IEnumerable<string?> values)
{
var h = HttpHeaders.GetOrAdd(name, _ => []);
h.AddRange(values);
return this;
}

/// <summary>
/// Provides the mocked response.
/// </summary>
Expand Down
20 changes: 17 additions & 3 deletions src/UnitTestEx/Schema/mock.unittestex.json
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,10 @@
"title": "The HTTP Response status code.",
"description": "Defaults to 200-OK where there is a corresponding Body; otherwise, 204-No content."
},
"headers": {
"$ref": "#/definitions/MockResponseHeaders",
"title": "The HTTP Response headers configuration."
},
"body": {
"type": "string",
"title": "The HTTP Response body (content)."
Expand All @@ -71,9 +75,19 @@
"description": "Defaults to 'application/json' where the Body contains JSON; otherwise, 'text/plain'."
}
}
},
"MockResponseHeaders": {
"title": "The HTTP Response headers configuration.",
"type": "object",
"additionalProperties": {
"type": "array",
"items": {
"type": "string"
}
}
},
"items": {
"$ref": "#/definitions/MockRequest"
}
},
"items": {
"$ref": "#/definitions/MockRequest"
}
}
7 changes: 6 additions & 1 deletion tests/UnitTestEx.NUnit.Test/MockHttpClientTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
using UnitTestEx.NUnit;
using NUnit.Framework;
using System.Diagnostics;
using System.Linq;

namespace UnitTestEx.NUnit.Test
{
Expand Down Expand Up @@ -410,7 +411,7 @@ public async Task DefaultHttpClient()
var mcf = MockHttpClientFactory.Create();
mcf.CreateDefaultClient(new Uri("https://d365test"))
.Request(HttpMethod.Post, "products/xyz").WithAnyBody()
.Respond.WithJson("{\"first\":\"Bob\",\"last\":\"Jane\"}", HttpStatusCode.Accepted);
.Respond.Headers([new("x-blah", "abc"), new("Age", "55")]).WithJson("{\"first\":\"Bob\",\"last\":\"Jane\"}", HttpStatusCode.Accepted);

var hc = mcf.GetHttpClient();
var res = await hc.PostAsJsonAsync("products/xyz", new Person { LastName = "Jane", FirstName = "Bob" }).ConfigureAwait(false);
Expand All @@ -419,6 +420,8 @@ public async Task DefaultHttpClient()
Assert.That(res.StatusCode, Is.EqualTo(HttpStatusCode.Accepted));
Assert.That(await res.Content.ReadAsStringAsync().ConfigureAwait(false), Is.EqualTo("{\"first\":\"Bob\",\"last\":\"Jane\"}"));
Assert.That(res.RequestMessage, Is.Not.Null);
Assert.That(res.Headers.GetValues("x-blah").Single(), Is.EqualTo("abc"));
Assert.That(res.Headers.Age, Is.EqualTo(TimeSpan.FromSeconds(55)));
});

Assert.ThrowsAsync<MockHttpClientException>(async () => await hc.SendAsync(new HttpRequestMessage(HttpMethod.Post, "products/xyz")));
Expand All @@ -443,6 +446,8 @@ public async Task WithYamlRequests()
{
Assert.That(res.StatusCode, Is.EqualTo(HttpStatusCode.OK));
ObjectComparer.JsonAssert("{\"first\":\"Bob\",\"last\":\"Jane\"}", await res.Content.ReadAsStringAsync().ConfigureAwait(false));
Assert.That(res.Headers.GetValues("x-blah").Single(), Is.EqualTo("abc"));
Assert.That(res.Headers.Age, Is.EqualTo(TimeSpan.FromSeconds(55)));
});

mcf.VerifyAll();
Expand Down
4 changes: 4 additions & 0 deletions tests/UnitTestEx.NUnit.Test/Resources/mock.unittestex.yaml
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
# yaml-language-server: $schema=https://raw.githubusercontent.com/Avanade/UnitTestEx/refs/heads/main/src/UnitTestEx/Schema/mock.unittestex.json
- method: post
uri: products/xyz
body: ^
Expand All @@ -9,6 +10,9 @@
- method: get
uri: people/123
response:
headers:
Age: [ '55' ]
x-blah: [ abc ]
body: |
{
"first":"Bob",
Expand Down

0 comments on commit c6bda58

Please sign in to comment.