Capture.Exception – BDD-esque w/ NUnit or: The Ikea Nightstand

UPDATE: After some feedback from Scott Bellware, Check below for the updated version.

I’ve been experimenting with BDD (or at least a BDD-inspired syntax, so as not to invoke the rage of the BDD mafia). I wove my own Spec class on top of NUnit back in this blog post. If you recall, I had encountered a problem however:

public class When_setting_the_password_on_a_null_user : PasswordServiceSpecification
{
    private string newPassword;

    protected override void EstablishContext()
    {
        base.EstablishContext();
        newPassword = "password";
    }

    protected override void Because()
    {
        passwordService.SetUserPassword(null, newPassword);
    }

    [Test]
    [ExpectedException(typeof(ArgumentNullException))]
    public void Should_throw_an_exception()
    {
        // Do Nothing
    }
}

This code doesn’t work. If I’m testing an error, and my Because throws the error, it asplodes. The exception isn’t generated in the [Test], it’s generated in the [SetUp] and NUnit doesn’t like that.

I was irritated. So I hacked something together. Something dark and horrible.

public class When_adding_a_null_Node_to_a_Graph : FeatureSpecification
{
    private CapturedException capturedException;

    protected override void Because()
    {
        capturedException = Capture.Exception(() => graph.AddNode(null));
    }

    [Test]
    [ExpectedException(typeof(ArgumentNullException))]
    public void Should_throw_an_exception()
    {
        capturedException.ReThrow();
    }
}

It’s a little… malevolent. But it works, and well, that’s ok with me. My ivory tower is more like an ivory nightstand. I put my phone on top of it, and my keys, and there’s a lamp with a cute picture of a kitten chewing on a puppies ear. And it’s not ivory, it’s Ikea.

So what powers my Ikea Nightstand? This:

public static class Capture
{
    public static CapturedException Exception(Action action)
    {
        var asyncResult = action.BeginInvoke(null, null);
        asyncResult.AsyncWaitHandle.WaitOne();

        return new CapturedException(action, asyncResult);
    }
}

public class CapturedException
{
    private Action action;
    private IAsyncResult asyncResult;

    public CapturedException(Action action, IAsyncResult asyncResult)
    {
        this.action = action;
        this.asyncResult = asyncResult;
    }

    public void ReThrow()
    {
        action.EndInvoke(asyncResult);
    }
}

I’m abusing just about everything here. CPS is going to knock on my door and ask me where my children are. My casual reply? “The wood chipper, where they belong.”

UPDATE: I GOT SCHOOLED!

Scott Bellware commented below about how I should just use an assert once I’ve captured the exception to make things more legible. Consider it done!

public class When_adding_a_null_Node_to_a_Graph : FeatureSpecification
{
    private Exception capturedException;

    protected override void Because()
    {
        capturedException = Capture.Exception(() => feature.AddNode(null));
    }

    [Test]
    public void Should_throw_an_exception()
    {
        Assert.That(capturedException, Is.InstanceOfType(typeof(ArgumentNullException)));
    }
}

And the accompanying new Capture.Exception():

public static class Capture
{
    public static Exception Exception(Action action)
    {
        try
        {
            action.Invoke();
        }
        catch (Exception e)
        {
            return e;
        }
        return null;
    }
}

Much simpler. Much cleaner. Much better.

Thanks, Mr. Bellware!

This entry was posted in Development and tagged , , . Bookmark the permalink.

3 Responses to Capture.Exception – BDD-esque w/ NUnit or: The Ikea Nightstand

  1. Once you have the exception saved as instance state in the spec class, you no longer need the ExpectedException attribute. You can verify that the exception is not null, and that the exception is the type that you expect.

    This will create a more readable test in the end.

  2. That does make a lot more sense. Thanks! I’ll update to show the second solution.

  3. Tim Erickson says:

    +1 for removing ExpectedException attribute and testing for not-null and expected type in test body.

Leave a Reply

Your email address will not be published. Required fields are marked *

*

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>