Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix FluentAssertions (actual body is not displayed in error message) #1085

Merged
merged 4 commits into from
Mar 20, 2024
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
Original file line number Diff line number Diff line change
@@ -1,15 +1,20 @@
#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;

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<WireMockAssertions> WithBody(string body, string because = "", params object[] becauseArgs)
Expand Down Expand Up @@ -56,7 +61,7 @@ public AndConstraint<WireMockAssertions> 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<WireMockAssertions> ExecuteAssertionWithBodyStringMatcher(
Expand All @@ -74,14 +79,14 @@ public AndConstraint<WireMockAssertions> WithBodyAsBytes(ExactObjectMatcher matc
.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);
Expand All @@ -104,48 +109,37 @@ public AndConstraint<WireMockAssertions> WithBodyAsBytes(ExactObjectMatcher matc
.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);

return new AndConstraint<WireMockAssertions>(this);
}

private AndConstraint<WireMockAssertions> ExecuteAssertionWithBodyAsBytesExactObjectMatcher(
ExactObjectMatcher matcher,
string because,
object[] becauseArgs,
Func<IReadOnlyList<IRequestMessage>, bool> condition,
Func<IReadOnlyList<IRequestMessage>, IReadOnlyList<IRequestMessage>> filter,
Func<IRequestMessage, object?> 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<string, StringPattern>[] 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<WireMockAssertions>(this);
private static string? FormatBodies(IEnumerable<object?> bodies)
{
var valueAsArray = bodies as object[] ?? bodies.ToArray();
return valueAsArray.Length == 1 ? FormatBody(valueAsArray.First()) : $"[ {string.Join(", ", valueAsArray.Select(FormatBody))} ]";
}
}
20 changes: 19 additions & 1 deletion src/WireMock.Net/Extensions/AnyOfExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,36 @@

namespace WireMock.Extensions;

internal static class AnyOfExtensions
/// <summary>
/// Some extensions for AnyOf.
/// </summary>
public static class AnyOfExtensions
{
/// <summary>
/// Gets the pattern.
/// </summary>
/// <param name="value">AnyOf type</param>
/// <returns>string value</returns>
public static string GetPattern(this AnyOf<string, StringPattern> value)
{
return value.IsFirst ? value.First : value.Second.Pattern;
}

/// <summary>
/// Converts a string-patterns to AnyOf patterns.
/// </summary>
/// <param name="patterns">The string patterns</param>
/// <returns>The AnyOf patterns</returns>
public static AnyOf<string, StringPattern>[] ToAnyOfPatterns(this IEnumerable<string> patterns)
{
return patterns.Select(p => p.ToAnyOfPattern()).ToArray();
}

/// <summary>
/// Converts a string-pattern to AnyOf pattern.
/// </summary>
/// <param name="pattern">The string pattern</param>
/// <returns>The AnyOf pattern</returns>
public static AnyOf<string, StringPattern> ToAnyOfPattern(this string pattern)
{
return new AnyOf<string, StringPattern>(pattern);
Expand Down
103 changes: 102 additions & 1 deletion test/WireMock.Net.Tests/FluentAssertions/WireMockAssertionsTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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<Exception>()
.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<Exception>()
.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<Exception>()
.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()
{
Expand Down
Loading