I'm using C# + xUnit, but my question can be applied to any language/test framework.
Per MSDN:
Avoid logic in tests
When writing your unit tests, avoid manual string concatenation, logical conditions, such as if, while, for, and switch, and other conditions.
Why? Less chance to introduce a bug inside of your tests. Focus on the end result, rather than implementation details. When you introduce logic into your test suite, the chance of introducing a bug into it increases dramatically. The last place that you want to find a bug is within your test suite. You should have a high level of confidence that your tests work, otherwise, you won't trust them. Tests that you don't trust, don't provide any value. When a test fails, you want to have a sense that something is wrong with your code and that it can't be ignored.
Tip If logic in your test seems unavoidable, consider splitting the test up into two or more different tests.
So in xUnit, we have theories that allow to stub different data as arguments. And it's fine for many cases like:
[Theory]
[InlineData(1, 2, 3)]
public void Sum_ShouldBeOk(int lhs, int rhs, int expectedResult)
{
// arrange
var calc = new Calculator();
// act
var result = calc.Sum(lhs, rhs);
// assert
Assert.Equal(result, expectedResult);
}
But what if I should test whether an exception is thrown or not? With FluentAssertions, it looks like this:
[Theory]
[InlineData(Role.User, true)]
[InlineData(Role.Admin, false)]
public void SpendAllMoney_ShouldNotBeAllowedForEveryone(Role role, bool shouldThrow)
{
// arrange
var wallet = new Wallet(100000M);
var user = new User(role);
// act
var act = () => wallet.Withdraw(user, "some wallet", 100000M);
// assert
if (shouldThrow)
{
act.Should().Throw<AccessDeniedException>();
}
else
{
act.Should().NotThrow();
}
}
In this case, branching seems unavoidable because there's no single method to check whether the exception is thrown or not: unlike returns, this is a different flow of program execution. To fix it I should copy-paste the whole test? Which may have quite a big setup specific for this test? So in order to fix the potential bug that can be cause by logic in tests, I should introduce code duplication that can cause its own bugs? Or make a method “setup whatever case” and call it in different methods?
It seems like extra work for the sake of following the best practices that doesn't solve anything.