diff --git a/src/WireMock.Net.FluentAssertions/Assertions/WireMockAssertions.WithBody.cs b/src/WireMock.Net.FluentAssertions/Assertions/WireMockAssertions.WithBody.cs index 129c1bab0..b9e364fc3 100644 --- a/src/WireMock.Net.FluentAssertions/Assertions/WireMockAssertions.WithBody.cs +++ b/src/WireMock.Net.FluentAssertions/Assertions/WireMockAssertions.WithBody.cs @@ -1,7 +1,12 @@ #pragma warning disable CS1591 using System; using System.Collections.Generic; +using AnyOfTypes; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using WireMock.Extensions; using WireMock.Matchers; +using WireMock.Models; // ReSharper disable once CheckNamespace namespace WireMock.FluentAssertions; @@ -9,7 +14,7 @@ namespace WireMock.FluentAssertions; public partial class WireMockAssertions { private const string MessageFormatNoCalls = "Expected {context:wiremockserver} to have been called using body {0}{reason}, but no calls were made."; - private const string MessageFormat = "Expected {context:wiremockserver} to have been called using body {0}{reason}, but didn't find it among the body {1}."; + private const string MessageFormat = "Expected {context:wiremockserver} to have been called using body {0}{reason}, but didn't find it among the body/bodies {1}."; [CustomAssertion] public AndConstraint WithBody(string body, string because = "", params object[] becauseArgs) @@ -56,7 +61,7 @@ public AndConstraint WithBodyAsBytes(ExactObjectMatcher matc { var (filter, condition) = BuildFilterAndCondition(r => r.BodyAsBytes, matcher); - return ExecuteAssertionWithBodyAsBytesExactObjectMatcher(matcher, because, becauseArgs, condition, filter, r => r.BodyAsBytes); + return ExecuteAssertionWithBodyAsIObjectMatcher(matcher, because, becauseArgs, condition, filter, r => r.BodyAsBytes); } private AndConstraint ExecuteAssertionWithBodyStringMatcher( @@ -74,14 +79,14 @@ private AndConstraint ExecuteAssertionWithBodyStringMatcher( .ForCondition(requests => CallsCount == 0 || requests.Any()) .FailWith( MessageFormatNoCalls, - matcher.GetPatterns() + FormatBody(matcher.GetPatterns()) ) .Then .ForCondition(condition) .FailWith( MessageFormat, - _ => matcher.GetPatterns(), - requests => requests.Select(expression) + _ => FormatBody(matcher.GetPatterns()), + requests => FormatBodies(requests.Select(expression)) ); FilterRequestMessages(filter); @@ -104,14 +109,14 @@ private AndConstraint ExecuteAssertionWithBodyAsIObjectMatch .ForCondition(requests => CallsCount == 0 || requests.Any()) .FailWith( MessageFormatNoCalls, - matcher.Value + FormatBody(matcher.Value) ) .Then .ForCondition(condition) .FailWith( MessageFormat, - _ => matcher.Value, - requests => requests.Select(expression) + _ => FormatBody(matcher.Value), + requests => FormatBodies(requests.Select(expression)) ); FilterRequestMessages(filter); @@ -119,33 +124,22 @@ private AndConstraint ExecuteAssertionWithBodyAsIObjectMatch return new AndConstraint(this); } - private AndConstraint ExecuteAssertionWithBodyAsBytesExactObjectMatcher( - ExactObjectMatcher matcher, - string because, - object[] becauseArgs, - Func, bool> condition, - Func, IReadOnlyList> filter, - Func expression - ) + private static string? FormatBody(object? body) { - Execute.Assertion - .BecauseOf(because, becauseArgs) - .Given(() => RequestMessages) - .ForCondition(requests => CallsCount == 0 || requests.Any()) - .FailWith( - MessageFormatNoCalls, - matcher.Value - ) - .Then - .ForCondition(condition) - .FailWith( - MessageFormat, - _ => matcher.Value, - requests => requests.Select(expression) - ); - - FilterRequestMessages(filter); + return body switch + { + null => null, + string str => str, + AnyOf[] stringPatterns => FormatBodies(stringPatterns.Select(p => p.GetPattern())), + byte[] bytes => $"byte[{bytes.Length}] {{...}}", + JToken jToken => jToken.ToString(Formatting.None), + _ => JToken.FromObject(body).ToString(Formatting.None) + }; + } - return new AndConstraint(this); + private static string? FormatBodies(IEnumerable bodies) + { + var valueAsArray = bodies as object[] ?? bodies.ToArray(); + return valueAsArray.Length == 1 ? FormatBody(valueAsArray.First()) : $"[ {string.Join(", ", valueAsArray.Select(FormatBody))} ]"; } } \ No newline at end of file diff --git a/src/WireMock.Net/Extensions/AnyOfExtensions.cs b/src/WireMock.Net/Extensions/AnyOfExtensions.cs index 41a752886..2a580227f 100644 --- a/src/WireMock.Net/Extensions/AnyOfExtensions.cs +++ b/src/WireMock.Net/Extensions/AnyOfExtensions.cs @@ -5,18 +5,36 @@ namespace WireMock.Extensions; -internal static class AnyOfExtensions +/// +/// Some extensions for AnyOf. +/// +public static class AnyOfExtensions { + /// + /// Gets the pattern. + /// + /// AnyOf type + /// string value public static string GetPattern(this AnyOf value) { return value.IsFirst ? value.First : value.Second.Pattern; } + /// + /// Converts a string-patterns to AnyOf patterns. + /// + /// The string patterns + /// The AnyOf patterns public static AnyOf[] ToAnyOfPatterns(this IEnumerable patterns) { return patterns.Select(p => p.ToAnyOfPattern()).ToArray(); } + /// + /// Converts a string-pattern to AnyOf pattern. + /// + /// The string pattern + /// The AnyOf pattern public static AnyOf ToAnyOfPattern(this string pattern) { return new AnyOf(pattern); diff --git a/test/WireMock.Net.Tests/FluentAssertions/WireMockAssertionsTests.cs b/test/WireMock.Net.Tests/FluentAssertions/WireMockAssertionsTests.cs index 266ef6883..455535bcd 100644 --- a/test/WireMock.Net.Tests/FluentAssertions/WireMockAssertionsTests.cs +++ b/test/WireMock.Net.Tests/FluentAssertions/WireMockAssertionsTests.cs @@ -702,7 +702,11 @@ public async Task HaveReceived1Call_WithBodyAsJson() // Act var httpClient = new HttpClient(); - await httpClient.PostAsync($"{server.Url}/a", new StringContent(@"{ ""x"": ""y"" }")); + var requestBody = new + { + x = "y" + }; + await httpClient.PostAsJsonAsync($"{server.Url}/a", requestBody); // Assert server @@ -740,6 +744,103 @@ public async Task HaveReceived1Call_WithBodyAsJson() server.Stop(); } + [Fact] + public async Task WithBodyAsJson_When_NoMatch_ShouldHaveCorrectErrorMessage() + { + // Arrange + var server = WireMockServer.Start(); + + server + .Given(Request.Create().WithPath("/a").UsingPost()) + .RespondWith(Response.Create().WithBody("A response")); + + // Act + var httpClient = new HttpClient(); + + var requestBody = new + { + x = "123" + }; + await httpClient.PostAsJsonAsync($"{server.Url}/a", requestBody); + + // Assert + Action act = () => server + .Should() + .HaveReceived(1) + .Calls() + .WithBodyAsJson(new { x = "y" }) + .And + .UsingPost(); + + act.Should() + .Throw() + .WithMessage("""Expected wiremockserver to have been called using body "{"x":"y"}", but didn't find it among the body/bodies "{"x":"123"}"."""); + + server.Stop(); + } + + [Fact] + public async Task WithBodyAsString_When_NoMatch_ShouldHaveCorrectErrorMessage() + { + // Arrange + var server = WireMockServer.Start(); + + server + .Given(Request.Create().WithPath("/a").UsingPost()) + .RespondWith(Response.Create().WithBody("A response")); + + // Act + var httpClient = new HttpClient(); + + await httpClient.PostAsync($"{server.Url}/a", new StringContent("123")); + + // Assert + Action act = () => server + .Should() + .HaveReceived(1) + .Calls() + .WithBody("abc") + .And + .UsingPost(); + + act.Should() + .Throw() + .WithMessage("""Expected wiremockserver to have been called using body "abc", but didn't find it among the body/bodies "123"."""); + + server.Stop(); + } + + [Fact] + public async Task WithBodyAsBytes_When_NoMatch_ShouldHaveCorrectErrorMessage() + { + // Arrange + var server = WireMockServer.Start(); + + server + .Given(Request.Create().WithPath("/a").UsingPost()) + .RespondWith(Response.Create().WithBody("A response")); + + // Act + var httpClient = new HttpClient(); + + await httpClient.PostAsync($"{server.Url}/a", new ByteArrayContent(new byte[] { 5 })); + + // Assert + Action act = () => server + .Should() + .HaveReceived(1) + .Calls() + .WithBodyAsBytes(new byte[] { 1 }) + .And + .UsingPost(); + + act.Should() + .Throw() + .WithMessage("""Expected wiremockserver to have been called using body "byte[1] {...}", but didn't find it among the body/bodies "byte[1] {...}"."""); + + server.Stop(); + } + [Fact] public async Task HaveReceived1Call_WithBodyAsBytes() {