1

I'm using NUnit 3 TestCaseData objects to feed test data to tests and Fluent Assertions library to check exceptions thrown.

Typically my TestCaseData object contains two parameters param1 and param2 used to create an instance of some object within the test and upon which I then invoke methods that should/should not throw exceptions, like this:

var subject = new Subject(param1, param2);
subject.Invoking(s => s.Add()).Should().NotThrow();

or

var subject = new Subject(param1, param2);
subject.Invoking(s => s.Add()).Should().Throw<ApplicationException>();

Is there a way to pass NotThrow() and Throw<ApplicationException>() parts as specific conditions in a third parameter in TestCaseData object to be used in the test? Basically I want to parameterize the test's expected result (it may be an exception of some type or no exception at all).

YMM
  • 632
  • 1
  • 10
  • 21

2 Answers2

1

[TestCaseData] is meant for Test Case Data, not for assertions methods.

I would keep the NotThrow and Throw in separate tests to maintain readability. If they share a lot of setup-logic, I would extract that into shared methods to reduce the size of the test method bodies.

TestCaseData accepts compile time values, whereas TestCaseSource generates them on runtime, which would be necessary to use Throw and NotThrow.

Here's a way to do it by misusing TestCaseSource. The result is an unreadable test method, so please don't use this anywhere.

Anyway here goes:

[TestFixture]
public class ActionTests
{
    private static IEnumerable<TestCaseData> ActionTestCaseData
    {
        get
        {
            yield return new TestCaseData((Action)(() => throw new Exception()), (Action<Action>)(act => act.Should().Throw<Exception>()));
            yield return new TestCaseData((Action)(() => {}), (Action<Action>)(act => act.Should().NotThrow()));
        }
    }

    [Test]
    [TestCaseSource(typeof(ActionTests), nameof(ActionTestCaseData))]
    public void Calculate_Success(Action act, Action<Action> assert)
    {
        assert(act);
    }
}
Jonas Nyrup
  • 2,376
  • 18
  • 25
0

I ended up using this:

using ExceptionResult = Action<System.Func<UserDetail>>;

        [Test]
        [TestCaseSource(typeof(UserEndpointTests), nameof(AddUserTestCases))]
        public void User_Add(string creatorUsername, Role role, ExceptionResult result)
        {
            var endpoint = new UserEndpoint(creatorUsername);
            var person = GeneratePerson();
            var request = GenerateCreateUserRequest(person, role);

            // Assertion comes here
            result(endpoint.Invoking(e => e.Add(request)));
        }

        private static IEnumerable AddUserTestCases
        {
            get
            {
                yield return new TestCaseData(TestUserEmail, Role.User, new ExceptionResult(x => x.Should().Throw<ApplicationException>())
                    .SetName("{m} (Regular User => Regular User)")
                    .SetDescription("User with Regular User role cannot add any users.");

                yield return new TestCaseData(TestAdminEmail, Role.Admin, new ExceptionResult(x => x.Should().NotThrow())
                    )
                    .SetName("{m} (Admin => Admin)")
                    .SetDescription("User with Admin role adds another user with Admin role.");
            }
        }

No big issues with readability, besides, SetName() and SetDescription() methods in the test case source help with that.

YMM
  • 632
  • 1
  • 10
  • 21