XUnit —— Record.Exception —— Stop Using Assert.Throws in Your BDD Unit Tests

原文:https://www.richard-banks.org/2015/07/stop-using-assertthrows-in-your-bdd.html

Stop Using Assert.Throws in Your BDD Unit Tests

 

I’m sure we’ve all seen the Assert.Throws assertion in test code by now, but just in case you haven’t, here's a simple example:

1
2
3
4
5
6
[Test]
public void InsertTestNameHere()
{
    var input = "a string";
    Assert.Throws<FormatException>(() => int.Parse(input));
}

If we consider this from an Arrange-Act-Assert (AAA) perspective it’s pretty easy to see that the Act and Assert logic are in a single line. It’s a very common pattern. It works, it works well, and the readability is fine, but if we start using a BDD approach to our unit testing (e.g. with SpecFlow) or we want to explicitly keep the Arrange, Act and Assert sections of our test code separated then Assert.Throws gets in the way.
To fix this we need a way of catching the exception and treating it like a normal object. In NUnit, the Assert.Catch method is for just this purpose. Here’s the adjusted code (using NUnit)

1
2
3
4
5
6
7
[Test]
public void InsertTestNameHere()
{
    var input = "a string";
    var exception = Assert.Catch(() => int.Parse(input));
    Assert.IsInstanceOf<FormatException>(exception);
}

In this case we’re catching any exception that int.Parse might throw. If we were more explicit and used Assert.Catch<FormatException>(), NUnit’s behaviour becomes much the same as Assert.Throws, and the test fails immediately if the expected exception isn’t detected. This isn't a behaviour we want, which is why we're using the generalised catch method. Now, since we have our exception in a variable, we can check if it’s the one we expected.
Oh, I forgot to mention it, but if no exception is thrown, Assert.Catch() fails the test immediately. It is an assertion after all. I’m sort of OK with this… but not really, as we’re still mixing assertions and actions on the same line and haven't resolved my original complaint.
For this reason I prefer XUnit’s approach.

1
2
3
4
5
6
7
8
[Fact]
public void InsertTestNameHere()
{
    var input = "a string";
    var exception = Record.Exception(() => int.Parse(input));
    Assert.NotNull(exception);
    Assert.IsType<FormatException>(exception);
}

The Record.Exception() method won't fail the test, regardless of what happens in the method. Unlike the NUnit approach, it merely records any exception arising from the call or returns null if no exception was thrown. Personally, I feel this approach is a better way to write tests around exceptions while still remaining consistent with the AAA approach. It also works well when using a BDD testing framework that separates the given/when/then step implementations as you can pass the result of the “when” method to the “then” methods with ease.

 

posted @ 2023-05-08 11:24  PanPan003  阅读(16)  评论(0编辑  收藏  举报