WireMock-Net / WireMock.Net

WireMock.Net is a flexible product for stubbing and mocking web HTTP responses using advanced request matching and response templating. Based on the functionality from http://WireMock.org, but extended with more functionality.
Apache License 2.0
1.35k stars 197 forks source link

FluentAssertions - Actual body is not displayed in error message when using Json Body #1084

Closed srollinet closed 3 months ago

srollinet commented 3 months ago

Describe the bug

When using a Json Body, Fluent assertion cannot format the actual body in the error message

Expected wiremockserver to have been called using body { param = value2 }, but didn't find it among the body {{{{empty}}}}.

Expected behavior:

The json body is correctly formatted

Expected wiremockserver to have been called using body { param = value2 }, but didn't find it among the body {{ "param": "value" }}.

Test to reproduce

You can reproduce the message with this unit test (proper assertion is not done, the test fails with the unformatted message)

    [Fact]
    public async Task HaveReceived1Call_WithBodyAsJson_UsingJsonMatcher()
    {
        // Arrange
        var server = WireMockServer.Start();

        server
            .Given(Request.Create().WithPath("/a").UsingPost())
            .RespondWith(Response.Create().WithBody("A response"));

        // Act
        var httpClient = new HttpClient();

        var body = new
        {
            param = "value"
        };
        await httpClient.PostAsJsonAsync($"{server.Url}/a", body);

        body = new
        {
            param = "value2"
        };
        // Assert
        server
            .Should()
            .HaveReceived(1)
            .Calls()
            .WithBodyAsJson(new JsonMatcher(body))
            .And
            .UsingPost();

        server.Stop();
    }

Other related info

One solution is to implement a custom IValueFormatter

Example:

public class JObjectFormatter : IValueFormatter
{
    public bool CanHandle(object value)
    {
        return value is JObject;
    }

    public void Format(object value, FormattedObjectGraph formattedGraph, FormattingContext context, FormatChild formatChild)
    {
        var jObject = (JObject)value;
        formattedGraph.AddFragment(jObject.ToString());
    }
}

And to add it in the custom formatters Formatter.AddFormatter(new JObjectFormatter());

Note: the IValueFormatter interface varies based on the target framework, so it must be multiple implementations

Let me know if I can help or if I can make a PR for this issue

StefH commented 3 months ago

@srollinet Could it also be an option to just create a helper method in WireMockAssertions.WithBody.cs which formats all possible body values?

srollinet commented 3 months ago

It could also probably be an option. I can check that next day if you want.

StefH commented 3 months ago

Sure. A PR is welcome.

srollinet commented 3 months ago

Okay, doing something like this makes the message better

    public AndConstraint<WireMockAssertions> WithBodyAsJson(IObjectMatcher matcher, string because = "", params object[] becauseArgs)
    {
        var (filter, condition) = BuildFilterAndCondition(r => r.BodyAsJson, matcher);

        return ExecuteAssertionWithBodyAsIObjectMatcher(matcher, because, becauseArgs, condition, filter, r => FormatBody(r.BodyAsJson));
    }

    private static object? FormatBody(object? body)
    {
        if (body is JToken jToken)
        {
            return jToken.ToString(Formatting.None);
        }

        //TODO Add other types here

        return body;
    }

Expected wiremockserver to have been called using body { param = value2, otherParam = 18067588-16df-4f27-ab2b-a737206f97ff }, but didn't find it among the body {"{"param":"value","otherParam":42}"}.

But it is still different between the expected body and the actual body

And it is worse if the Matcher is created by using a named type, or even worse if the type overrides ToString()

Expected wiremockserver to have been called using body WireMock.Net.Tests.FluentAssertions.WireMockAssertionsTests+RequestInputDto { OtherParam = {718de8cd-21ef-4406-b631-1cd99c1a4328}, Param = "value" }, but didn't find it among the body {"{"Param":"value","OtherParam":"6d3380ff-bfcf-4269-8d81-01ac24d13645"}"}.

I wonder if the expected body should also be serialized as json, so both representations will be similar. What do you think?

StefH commented 3 months ago

@srollinet Can you take a look at my PR ?

srollinet commented 3 months ago

@StefH Yes, it looks good to me