Search code examples
c#fluent-assertions

C# FluentAssertions continue after Failed Assertion


Is it possible to continue after a failed Assertion in FluentAssertions? I had some assertions which are no show stoppers and should only be reported but not failing the Test run.

[TestClass]
public class UnitTest1
{
    [TestMethod]
    public void TestMethod1()
    {
        using (var scope = new AssertionScope())
        {
            "This Should not Failed with an AssertException".Should().Be("Should Failed");
            "And this also not".Should().Be("Should Failed");
            "All should only be printed to the console".Should().NotBeEmpty();
        }
        "But the Test should continue".Should().Be("And Failed here with an AssertException");
    }
}

Solution

  • For the output side, use ITestOutputHelper from XUnit — it's the only way to get test log output in XUnit 2.0+. If you must write the checks as assertions, you can provide your own implementation of IAssertionStrategy as a constructor parameter to AssertionScope, and have it send assertion failure messages to XUnit's test output instead of throwing.

    Note: You'll need at least v5.9.0 of FluentAssertions to pull this off.

    public class XUnitTestOutputAssertionStrategy : IAssertionStrategy
    {
        private readonly ITestOutputHelper output;
        private readonly List<string> failures = new List<string>();
    
        public XUnitTestOutputAssertionStrategy(ITestOutputHelper output)
        {
            this.output = output;
        }
    
        public void HandleFailure(string message)
        {
            failures.Add(message);
        }
    
        public IEnumerable<string> DiscardFailures()
        {
            var snapshot = failures.ToArray();
            failures.Clear();
            return snapshot;
        }
    
        public void ThrowIfAny(IDictionary<string, object> context)
        {
            if (!failures.Any()) return;
    
            var sb = new StringBuilder();
            sb.AppendLine(string.Join(Environment.NewLine, failures));
            foreach ((string key, object value) in context)
                sb.AppendFormat("\nWith {0}:\n{1}", key, value);
    
            output.WriteLine(sb.ToString());
        }
    
        public IEnumerable<string> FailureMessages => failures;
    }
    
    public class ContrivedTests
    {
        private readonly ITestOutputHelper output;
    
        public ContrivedTests(ITestOutputHelper output)
        {
            this.output = output;
        }
    
        [Fact]
        public void WhenRunningTest_WithContrivedExample_ShouldOutputThenAssert()
        {
            using (new AssertionScope(new XUnitTestOutputAssertionStrategy(output)))
            {
                "Failures will log".Should().Contain("nope", "because we want to log this");
                "Success won't log".Should().StartWith("Success", "because we want to log this too, but it succeeded");
            }
    
            "This line will fail the test".Should().StartWith("Bottom Text", "because I pulled a sneaky on ya");
        }
    }