From 6f569696e07c94ea41baf80bea364fb142f073ad Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 10 Mar 2026 18:34:07 +0000 Subject: [PATCH 1/2] Initial plan From 8989ad288545c222ee0d3a1328193e3777338594 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 10 Mar 2026 18:40:33 +0000 Subject: [PATCH 2/2] Add Using(JsonSerializerOptions) method to RequestMockBuilder Co-authored-by: dennisdoomen <572734+dennisdoomen@users.noreply.github.com> --- .../ApprovedApi/net472.verified.txt | 1 + .../ApprovedApi/net8.0.verified.txt | 1 + Mockly.Specs/HttpMockSpecs.cs | 57 +++++++++++++++++++ Mockly/RequestMockBuilder.cs | 30 ++++++++-- 4 files changed, 85 insertions(+), 4 deletions(-) diff --git a/Mockly.ApiVerificationTests/ApprovedApi/net472.verified.txt b/Mockly.ApiVerificationTests/ApprovedApi/net472.verified.txt index 473e624..e41d11f 100644 --- a/Mockly.ApiVerificationTests/ApprovedApi/net472.verified.txt +++ b/Mockly.ApiVerificationTests/ApprovedApi/net472.verified.txt @@ -126,6 +126,7 @@ namespace Mockly public Mockly.RequestMockResponseBuilder RespondsWithODataResult(System.Net.HttpStatusCode statusCode, System.Collections.Generic.IEnumerable> builders) { } public Mockly.RequestMockResponseBuilder RespondsWithODataResult(System.Net.HttpStatusCode statusCode, System.Collections.Generic.IEnumerable> builders, string odataContext) { } public Mockly.RequestMockResponseBuilder RespondsWithStatus(System.Net.HttpStatusCode statusCode) { } + public Mockly.RequestMockBuilder Using(System.Text.Json.JsonSerializerOptions options) { } public Mockly.RequestMockBuilder With(System.Func> matcher, [System.Runtime.CompilerServices.CallerArgumentExpression("matcher")] string? matcherText = null) { } public Mockly.RequestMockBuilder With(System.Func matcher, [System.Runtime.CompilerServices.CallerArgumentExpression("matcher")] string? matcherText = null) { } public Mockly.RequestMockBuilder WithAnyQuery() { } diff --git a/Mockly.ApiVerificationTests/ApprovedApi/net8.0.verified.txt b/Mockly.ApiVerificationTests/ApprovedApi/net8.0.verified.txt index f428cee..b2b4b6c 100644 --- a/Mockly.ApiVerificationTests/ApprovedApi/net8.0.verified.txt +++ b/Mockly.ApiVerificationTests/ApprovedApi/net8.0.verified.txt @@ -129,6 +129,7 @@ namespace Mockly public Mockly.RequestMockResponseBuilder RespondsWithODataResult(System.Net.HttpStatusCode statusCode, System.Collections.Generic.IEnumerable> builders) { } public Mockly.RequestMockResponseBuilder RespondsWithODataResult(System.Net.HttpStatusCode statusCode, System.Collections.Generic.IEnumerable> builders, string odataContext) { } public Mockly.RequestMockResponseBuilder RespondsWithStatus(System.Net.HttpStatusCode statusCode) { } + public Mockly.RequestMockBuilder Using(System.Text.Json.JsonSerializerOptions options) { } public Mockly.RequestMockBuilder With(System.Func> matcher, [System.Runtime.CompilerServices.CallerArgumentExpression("matcher")] string? matcherText = null) { } public Mockly.RequestMockBuilder With(System.Func matcher, [System.Runtime.CompilerServices.CallerArgumentExpression("matcher")] string? matcherText = null) { } public Mockly.RequestMockBuilder WithAnyQuery() { } diff --git a/Mockly.Specs/HttpMockSpecs.cs b/Mockly.Specs/HttpMockSpecs.cs index d55e7d0..e5c69cc 100644 --- a/Mockly.Specs/HttpMockSpecs.cs +++ b/Mockly.Specs/HttpMockSpecs.cs @@ -4,6 +4,7 @@ using System.Net.Http; using System.Net.Http.Headers; using System.Text; +using System.Text.Json; using System.Threading.Tasks; using FluentAssertions; using Xunit; @@ -2087,4 +2088,60 @@ await response.Should().BeEquivalentTo(new }); } } + + public class WhenUsingCustomJsonSerializerOptions + { + [Fact] + public async Task Can_use_custom_options_for_json_response_content() + { + // Arrange + var mock = new HttpMock(); + + var options = new JsonSerializerOptions + { + PropertyNamingPolicy = JsonNamingPolicy.CamelCase + }; + + mock.ForGet() + .WithPath("/api/data") + .Using(options) + .RespondsWithJsonContent(new { UserId = 42, UserName = "Alice" }); + + // Act + var response = await mock.GetClient().GetAsync("https://localhost/api/data"); + var body = await response.Content.ReadAsStringAsync(); + + // Assert + response.StatusCode.Should().Be(HttpStatusCode.OK); + body.Should().Contain("\"userId\"").And.Contain("\"userName\""); + body.Should().NotContain("\"UserId\"").And.NotContain("\"UserName\""); + } + + [Fact] + public async Task Can_use_custom_options_to_match_request_body_as_object() + { + // Arrange + var mock = new HttpMock(); + + var options = new JsonSerializerOptions + { + PropertyNamingPolicy = JsonNamingPolicy.CamelCase + }; + + mock.ForPost() + .WithPath("/api/data") + .Using(options) + .WithBody(new { UserId = 42, UserName = "Alice" }) + .RespondsWithStatus(HttpStatusCode.NoContent); + + var client = mock.GetClient(); + + // Act: Send camelCase JSON (matching what camelCase options would serialize to) + var response = await client.PostAsync("https://localhost/api/data", + new StringContent("{\"userId\":42,\"userName\":\"Alice\"}")); + + // Assert + response.StatusCode.Should().Be(HttpStatusCode.NoContent); + } + } } diff --git a/Mockly/RequestMockBuilder.cs b/Mockly/RequestMockBuilder.cs index 0982426..3b2b395 100644 --- a/Mockly/RequestMockBuilder.cs +++ b/Mockly/RequestMockBuilder.cs @@ -24,6 +24,7 @@ public class RequestMockBuilder private string? scheme = "https"; private string? hostPattern = "localhost"; private RequestCollection? requestCollection; + private JsonSerializerOptions? jsonSerializerOptions; internal RequestMockBuilder(HttpMock mockBuilder, HttpMethod method) { @@ -194,7 +195,7 @@ public RequestMockBuilder WithBody(object body) throw new ArgumentNullException(nameof(body)); } - var json = JsonSerializer.Serialize(body); + var json = JsonSerializer.Serialize(body, jsonSerializerOptions); return WithBodyMatchingJson(json); } @@ -244,6 +245,21 @@ public RequestMockBuilder CollectingRequestsIn(RequestCollection collection) return this; } + /// + /// Specifies the to use for all JSON serialization in this mock, + /// including body matching and response content generation. + /// + public RequestMockBuilder Using(JsonSerializerOptions options) + { + if (options is null) + { + throw new ArgumentNullException(nameof(options)); + } + + jsonSerializerOptions = options; + return this; + } + /// /// Responds with the specified HTTP status code. /// @@ -288,6 +304,8 @@ public RequestMockResponseBuilder RespondsWithJsonContent(IResponseBuilder /// public RequestMockResponseBuilder RespondsWithJsonContent(HttpStatusCode statusCode, object content) { + var options = jsonSerializerOptions; + var mock = new RequestMock { Method = Method, @@ -299,7 +317,7 @@ public RequestMockResponseBuilder RespondsWithJsonContent(HttpStatusCode statusC RequestCollection = requestCollection, Responder = _ => { - var json = JsonSerializer.Serialize(content); + var json = JsonSerializer.Serialize(content, options); return new HttpResponseMessage(statusCode) { Content = new StringContent(json, Encoding.UTF8, "application/json") @@ -392,6 +410,8 @@ public RequestMockResponseBuilder RespondsWithODataResult(IEnumerable value) { + var options = jsonSerializerOptions; + var mock = new RequestMock { Method = Method, @@ -408,7 +428,7 @@ public RequestMockResponseBuilder RespondsWithODataResult(HttpStatusCode statusC ["value"] = value.ToArray() }; - string json = JsonSerializer.Serialize(payload); + string json = JsonSerializer.Serialize(payload, options); return new HttpResponseMessage(statusCode) { Content = new StringContent(json, Encoding.UTF8, "application/json") @@ -439,6 +459,8 @@ public RequestMockResponseBuilder RespondsWithODataResult(HttpStatusCode stat public RequestMockResponseBuilder RespondsWithODataResult(HttpStatusCode statusCode, IEnumerable value, string odataContext) { + var options = jsonSerializerOptions; + var mock = new RequestMock { Method = Method, @@ -460,7 +482,7 @@ public RequestMockResponseBuilder RespondsWithODataResult(HttpStatusCode statusC payload["@odata.context"] = odataContext; } - string json = JsonSerializer.Serialize(payload); + string json = JsonSerializer.Serialize(payload, options); return new HttpResponseMessage(statusCode) { Content = new StringContent(json, Encoding.UTF8, "application/json")