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!