Search code examples
c#nunitnsubstitutefluent-assertions

Check return value in FluentAssertion exception syntax


I'd like to check the return value from the method by FluentAssertion syntax. Please consider the following snippet:

public interface IFoo
{
    Task<int> DoSomething();
}

public class Bar
{
    private readonly IFoo _foo;
    private static int _someMagicNumber = 17;

    public Bar(IFoo foo)
    {
        _foo = foo;
    }

    public async Task<int> DoSomethingSmart()
    {
        try
        {
            return await _foo.DoSomething();
        }
        catch
        {
            return _someMagicNumber;
        }
    }
}

[TestFixture]
public class BarTests
{
    [Test]
    public async Task ShouldCatchException()
    {
        // Arrange
        var foo = Substitute.For<IFoo>();
        foo.DoSomething().Throws(new Exception());
        var bar = new Bar(foo);
        Func<Task> result = () => bar.DoSomethingSmart();

        // Act-Assert
        await result.Should().NotThrowAsync();
    }

    [Test]
    public async Task ShouldReturnDefaultValueWhenExceptionWasThrown()
    {
        // Arrange
        var foo = Substitute.For<IFoo>();
        foo.DoSomething().Throws(new Exception());
        var bar = new Bar(foo);

        // Act
        var result = await bar.DoSomethingSmart();

        // Assert
        result.Should().Be(17);
    }
}

My goal is combining those two tests into the new one, but I'd like to preserve the fluent assertion check: result.Should().NotThrowAsync();

So my question is how to check in the first test that the return value is 17 in my example?


Solution

  • The current version of Fluent Assertions (5.5.3) does not distinguish between Func<Task> and Func<Task<T>>. Both types are handled by AsyncFunctionAssertions, which assigns it to a Func<Task> and hence looses the return value for Task<T>.

    One way to circumvent this, is to assign the return value to a local variable.

    [Test]
    public async Task ShouldCatchException()
    {
        // Arrange
        var foo = Substitute.For<IFoo>();
        foo.DoSomething().Throws(new Exception());
        var bar = new Bar(foo);
    
        // Act
        int? result = null;
        Func<Task> act = async () => result = await bar.DoSomethingSmart();
    
        // Act-Assert
        await act.Should().NotThrowAsync();
        result.Should().Be(17);
    }
    

    I've created an issue on the Fluent Assertion issue tracker.

    edit:

    Fluent Assertions 6.0.0 added support for Task<T>, so you can continue asserting on the result of DoSomethingSmart.

    // Arrange
    var foo = Substitute.For<IFoo>();
    foo.DoSomething().Throws(new Exception());
    var bar = new Bar(foo);
    
    // Act
    Func<Task<int>> act = () => bar.DoSomethingSmart();
    
    // Act-Assert
    (await act.Should().NotThrowAsync()).Which.Should().Be(17);
    

    There's also a new neat helper WithResult for async methods to avoid the extra set of parentheses.

    // Arrange
    var foo = Substitute.For<IFoo>();
    foo.DoSomething().Throws(new Exception());
    var bar = new Bar(foo);
    
    // Act
    Func<Task<int>> act = () => bar.DoSomethingSmart();
    
    // Act-Assert
    await act.Should().NotThrowAsync().WithResult(17);