Search code examples
c#cucumbergherkinfluent-assertions

How can I hook in to all assertion failures in gherkin step definitions?


I want to run some custom reporting logic when any step in my gherkin scenario fails. We're using FluentAssertions to run the actual tests within the Gherkin steps, and I though I might be able to hook into the Assertion failures somehow to also run my own code.

GitHub Copilot came up with this gem for me:

using FluentAssertions;
using FluentAssertions.Execution;

[Binding]
public class CommonStepsDefinitions
{
    // ...

    [BeforeScenario]
    public void BeforeScenario()
    {
        AssertionOptions.AssertionFailed += OnAssertionFailed;
    }

    [AfterScenario]
    public void AfterScenario()
    {
        AssertionOptions.AssertionFailed -= OnAssertionFailed;
    }

    private void OnAssertionFailed(AssertionFailedException exception)
    {
        // Custom logic here. For example, you could log the exception:
        _logger.LogError(exception, "An assertion failed.");
    }

    // ...
}

but it seems to be hallucinating that, as I can't find any documentation from FluentAssertions covering that, and the code straight up doesn't compile.

When I pressed copilot for a better explanation, it suggested I create my own wrapper to catch the exception thrown from a FluentAssertion statement:

public void AssertWithLogging(Action assert)
{
    try
    {
        assert();
    }
    catch (Exception ex)
    {
        _logger.LogError(ex, "An assertion failed.");
        throw;
    }
}

// ...

AssertWithLogging(() => assertion.Should().BeTrue());

It's the best option I've got so far, but my problem with it is that it would be very easy to forget to wrap future assertions as we develop new steps.

Are there any better options, or features of either FluentAssertions or Gherkin/Cucumber that I'm missing?


Solution

  • My investigations continued and I came across the AfterStep attribute, allowing me to check for a fail state after each step.

    Inside the AfterStep handler, we can use the static ScenarioStepContext.Current to access the context for the current (failing) step and allow us to trigger our reporting logic based on the step information.

    [AfterStep]
    public void AfterStep()
    {
        if (ScenarioStepContext.Current.Status == ScenarioExecutionStatus.TestError)
        {
            //the step threw an exception
            _logger.LogError(ScenarioStepContext.Current.TestError, "a step failed");
        }
    }