Geeks With Blogs
Boy Meets 'Hello World' Blogging the journey from College Grad to .NET Developer
Note: For users of NUnit, version 2.5 will have an Assert.Throws method. This version is currently available in alpha status.

On the ALT.NET mailing list, there was a discussion about using the [ExpectedException] attribute in NUnit. Most people have come to figure out that using the ExpectedException attribute on your tests has disadvantages. First, your test will not be able to pinpoint exactly where the exception is supposed to occur. You could get false negatives for having the exception thrown in the wrong method. As an example, imagine you are testing that an ArgumentNullException is thrown when you call a method:

[Test]
[ExpectedException(typeof(ArgumentNullException))]
public void ErrorWhenTryingToAddNullMonkey()
{
   ILogger logger = BuildStubLogger();
   MonkeyCage monkeyCage = new MonkeyCage(logger);

   monkeyCage.AddMonkey(null);
}

In this example, you're trying to test that the ArgumentNullException gets thrown because we're trying to add a null Monkey. However, this test might pass if we accidentally made BuildStubLogger() return null, and our constructor is the code throwing the ArgumentNullException for the null logger.

 

Second, you cannot be sure that the correct arguments were passed to the exception. Let's assume that you fixed your problem above with BuildStubLogger(), and the ArgumentNullException is thrown from AddMonkey. Maybe you want to test to make sure that the ArgumentNullException was thrown with the correct paramName argument. There's no way of doing this with the ExpectedException attribute. Knowing this, you may start creating tests like this...

   1:  
   1:  [Test]
   2:  public void ErrorWhenTryingToAddNullMonkey()
   3:  {
   4:     ILogger logger = BuildStubLogger();
   5:     MonkeyCage monkeyCage = new MonkeyCage(logger);
   6:   
   7:     try
   8:     {
   9:        monkeyCage.AddMonkey(null);
  10:        Assert.Fail("Exception never thrown.");
  11:     }
  12:     catch (ArgumentNullException ex)
  13:     {
  14:        Assert.That(ex.ParamName, Is.EqualTo("monkey"), "Exception thrown, but has incorrect param name.");
  15:     }
  16:  }

Our try block will catch the exception (and the test will fail if any other exception is thrown). Also, on line 10, we have an Assert.Fail to ensure that if the exception is not thrown, that the test fails. The problem here is pretty obvious: it's a lot of code for such a simple idea. After writing this multiple times, you could very easily start missing key parts, most notably being forgetting that Assert.Fail() call.

 

xUnit, the newest test framework on the block, has a nice way of dealing with this, using an Assert.Throws method that allows you to specify where the exception should be thrown and what the exception should look like. However, I'm not going to switch testing frameworks just for this. And, it's pretty simple to write your own. Here's what the results could look like...

public static class AssertThrows<T>
        where T : Exception
    {
        public delegate void NormalCode();
        public delegate void AssertionCode(T exception);
        
        public static void For(NormalCode normalCode)
        {
            For(normalCode, delegate { });
        }

        public static void For(NormalCode normalCode, AssertionCode assertionCode)
        {
            bool expectionThrown = false;
            
            try
            {
                normalCode.Invoke();
            }
            catch (T ex)
            {
                assertionCode.Invoke(ex);
                expectionThrown = true;
            }

            if (!expectionThrown) throw new ExceptionNeverThrownException(typeof(T));
        }
    }

Here's how you would use it...

[Test]
public void ErrorWhenTryingToAddNullMonkey()
{
   ILogger logger = BuildStubLogger();
   MonkeyCage monkeyCage = new MonkeyCage(logger);

   AssertThrows<ArgumentNullException>.For(
      delegate { 
         monkeyCage.AddMonkey(null);
      },
      delegate (ArgumentNullException ex) {
         Assert.That(ex.ParamName, Is.EqualTo("monkey"));
      });
}
 

You can do whatever tricks you want to clean it up, but on .NET 2.0, this is ok for me. Since I'm moving over to .NET 3.5, the delegates will soon become lambda's.

Posted on Thursday, April 17, 2008 6:09 AM Unit Tests | Back to top


Comments on this post: Beyond ExpectedException

No comments posted yet.
Your comment:
 (will show your gravatar)


Copyright © mhildreth | Powered by: GeeksWithBlogs.net