Many people have heard and read my rants on ExpectedException in the past few months. If you have not heard it in short I don't believe you should use it. In fact, in xUnit.net there is no such attribute. However, people tell me great; I am not using xUnit.net and I still want to adopt the approach. Well there is good news on a couple of fronts. First, NUnit 2.5 which is currently in alpha has implemented something similar to Assert.Throws. Secondly, if you don't want to wait for NUnit 2.5 or you don't want to upgrade I have provided an implementation of Assert.Throws, Assert.DoesNotThrow, and Record.Exception that you can download from here.
One of the biggest flaws of ExpectedException at the method level is that it can often succeed for the wrong reasons. For example, read through the following code and think about whether the test passes or not.
[Test]
[ExpectedException(typeof(ArgumentException))]
public void DepositThrowsArgumentExceptionWhenZero()
{
CheckingAccount account = new CheckingAccount(0.00);
account.Deposit(0.00);
}
Here is the relevant CheckingAccount code:
public CheckingAccount(double balance)
{
if (balance == 0)
throw new ArgumentException("Initial balance cannot be zero");
this.balance = balance;
}
public void Deposit(double amount)
{
if (amount == 0)
throw new ArgumentException("Deposit amount cannot be zero");
balance += amount;
}
If you were to run this test in NUnit it would succeed. However if you look at the name of the test, DepositThrowsArgumentExceptionWhenZero it succeeds because the creation of the CheckingAccount class with an initial deposit of $0.00 also throws ArgumentException. A better way, in my opinion, uses the XunitAssert.Throws method to isolate the specific piece of code that throws the exception. Here is the same example using XunitAssert.Throws:
[Test]
public void DepositThrowsArgumentExceptionWhenZero()
{
CheckingAccount account = new CheckingAccount(0.00);
XunitAssert.Throws<ArgumentException>(
() => account.Deposit(0.00));
}
If you were to run this test the test would fail on construction of the CheckingAccount object. The only “flaw” in this approach and I mean flaw lightly is that XunitAssert.Throws does violate the 3A pattern. In the downloaded code there is an alternative called Record.Exception which facilitates the writing of the tests using 3A. Here is the same example using Record.Exception.
[Test]
public void DepositThrowsArgumentExceptionWhenZero()
{
CheckingAccount account = new CheckingAccount(0.00);
Exception exception =
Record.Exception(() => account.Deposit(0.00));
Assert.IsInstanceOfType(typeof(ArgumentException), exception);
}
I have described the above solutions using the .NET 3.5 Lambda syntax but all of the methods will work with a delegate and there are non-generic implementations as well. The sample code is licensed under the Microsoft Public License.
Download assert.zip (17.2K). I have tested the code with NUnit 2.4.6 as well at the testing tool in Visual Studio 2008 and the sample code includes the tests as well.
My biggest complaint about ExpectedException was that you were never able to validate that, amoung all exceptions of a particular type, that you were able to verify the correct message in a localizable manner.
So we, too, created an expected exception method, that took the expected exception method as well. We also went a little further, and took a params Action[] so we would assert for each test in a sequence within a particular method.
Posted by: Keith J. Farmer | June 27, 2008 at 02:58 PM
You could put details in the expected exception to isolate a particular exception.
[ExpectedException( typeof( ArgumentException ), ExpectedMessage="Deposit amount cannot be zero" )]
or
[ExpectedException( "System.ArgumentException", ExpectedMessage="Deposit amount cannot be zero" )]
Hence this code would fail because it had the wrong message. You would then change the test to fix the problem with the setup of checking account.
Posted by: WarePhreak | June 30, 2008 at 07:05 AM