JasperFx/alba

Does this look correct: a ContentShouldBeJson assertion helper

egil opened this issue · 2 comments

egil commented

Hi there,

First off, great library!

For a project, I needed a way to verify that the structure of the raw JSON returned from a request matches our API contract, so I created the following custom extension method.

However, I had to copy in the ReadBody method since it was internal, which leads me to believe it should either be public, or I am doing something wrong.

Here is my custom assertion helper, which uses the https://github.com/weichch/system-text-json-jsondiffpatch library to perform the JSON comparison:

internal static class AlbaScenarioAssertionExtensions
{
    private static readonly JsonSerializerOptions PrettyPrintOptions = new JsonSerializerOptions() { WriteIndented = true };

    public static Scenario ContentShouldBeJson(this Scenario scenario, string jsonString)
    {
        return scenario.AssertThat(new BodyJsonAssertion(jsonString));
    }

    private class BodyJsonAssertion : IScenarioAssertion
    {
        private readonly string jsonString;

        public BodyJsonAssertion(string jsonString)
        {
            this.jsonString = jsonString;
        }

        public void Assert(Scenario scenario, HttpContext context, ScenarioAssertionException ex)
        {
            var body = ReadBody(context);
            var actual = JsonNode.Parse(body);
            var expected = JsonNode.Parse(jsonString);

            if (!actual.DeepEquals(expected, JsonElementComparison.RawText))
            {
                ex.Add($"Expected the content to be:{Environment.NewLine}" +
                    $"{expected.ToJsonString(PrettyPrintOptions)}{Environment.NewLine}" +
                    $"but was:{Environment.NewLine}{actual.ToJsonString(PrettyPrintOptions)}");
            }
        }

        internal static string ReadBody(HttpContext context)
        {
            try
            {
                var stream = context.Response.Body;
                if (stream.CanSeek)
                {
                    stream.Position = 0;
                }

                return Encoding.UTF8.GetString(ReadAllBytes(stream));
            }
            catch (Exception)
            {
                return string.Empty;
            }
        }

        internal static byte[] ReadAllBytes(Stream stream)
        {
            using var content = new MemoryStream();
            stream.CopyTo(content);
            return content.ToArray();
        }
    }
}

Does this look OK or should I be doing something differently here?

Hawxy commented

This looks reasonable, structural matching is something that's outside of Alba's own toolkit. ReadBody is in the samples so it being internal is likely an oversight, I'll expose it in the next release.

egil commented

Cool. The alternative would be to lazily use it to set the Body property on the ScenarioAssertionException that is being passed to the assertion method. That would be my preference.