Arrange Act Assert Pattern

The Arrange, Act and Assert Pattern structures a test so that it reads in one direction. It is also known as Given, When, Then, which is why both ISyntax<TThis> spellings are supported.

Splitting a test into these three stages makes it obvious what is being tested and where the setup can be shared.

Bunsen Burner enforces the pattern with the compiler. It does that in a few ways,

  • The DSL enforces the correct flow, you need to arrange, act and assert in the correct order, otherwise it will not compile

  • Uses types to drive what each stage in the DSL works on, this allows IDEs to lift code out of the DSL and into utility functions that can then be shared

    
    [Fact(DisplayName = "SerializeAsync can work with anonymous objects")]
    public Task ExampleTestUsingAaaBuilder2() =>
        Arrange(() =>
            {
                var widget = new { Name = "Widget1", Cost = 12.50 };
                var ms = new MemoryStream();
                return (widget, ms);
            })
            // pull out the shared test code
            .Act(CallSerializeAsync)
            .Assert(result =>
                Assert.Equal(expected: "{\"Name\":\"Widget1\",\"Cost\":12.5}", actual: result)
            );
    
    // this is now a shared stage and can be used in many tests
    private static async Task<string> CallSerializeAsync<T>((T, MemoryStream) data)
    {
        var (widget, ms) = data;
        await JsonSerializer.SerializeAsync(ms, widget);
        return Encoding.UTF8.GetString(ms.ToArray());
    }
    
    
  • Provides additional And stages to promote short stages, particularly helpful in the assertion stage

    
    [Fact(DisplayName = "SerializeAsync can work with anonymous objects")]
    public Task ExampleTest() =>
        Arrange(() =>
            {
                var widget = new { Name = "Widget1", Cost = 12.50 };
                var ms = new MemoryStream();
                return (widget, ms);
            })
            .Act(async data =>
            {
                await JsonSerializer.SerializeAsync(
                    data.ms,
                    data.widget,
                    cancellationToken: TestContext.Current.CancellationToken
                );
                return Encoding.UTF8.GetString(data.ms.ToArray());
            })
            // split assertions into parts
            .Assert(result => !string.IsNullOrWhiteSpace(result))
            // And can only follow after at least one Assert
            .And(result => result.Contains("Widget1"))
            // can have as many And calls as required
            .And(result => result.Contains("12.5"));