NUnit, Neither Assert.Throws nor [ExpectedExceptio

2019-07-26 22:06发布

问题:

Before I start, I want to make it clear that I've already checked for solutions in both this question and this question.

The Method to Test

public static DataSet ExecuteDataSet(this SqlConnection connection, string sql)
{
    if (null == connection || null == sql)
    {
        throw new ArgumentNullException();
    }

    using (var command = connection.CreateCommand())
    {
        // Code elided for brevity
    }
}

The Test Methods

[Test]
[ExpectedException(typeof(ArgumentNullException))]
public void ExecuteDataSetThrowsForNullConnection()
{
    ((SqlConnection)null).ExecuteDataSet("SELECT TOP 1 * FROM prep");
}

[Test]
public void ExecuteDataSetThrowsForNullSql()
{
    Assert.Throws<ArgumentNullException>(
        () => Resource.Connection.ExecuteDataSet(null)
    );
}

The Odd Behavior

Neither version of the test method is catching the ArgumentNullException that is thrown immediately upon entering the ExecuteDataSet method. Control flow proceeds to the next line (using (var command = connection.CreateCommand())) and a NullReferenceException is occurring instead (which, of course, isn't handled by either of my test cases because it should never be thrown).

Originally, the first test method (ExecuteDataSetThrowsForNullConnection) looked just like the second one (ExecuteDataSetThrowsForNullSql). When Assert.Throws failed to catch the exception, I did some research and noted that some folks recommended using ExpectedException instead. I modified the test code accordingly, but to no avail.

For the record, this is 32-bit .NET 3.5 code, tested under NUnit 2.5.9. I'm using TestDriven.NET for Visual Studio integration, and have the latest versions of NCover and NDepend installed.

TL;DR Question

Why aren't the test methods catching the exception that is thrown, and how do I fix it?

EDIT

This version of the test method works.

[Test]
public void ExecuteDataSetThrowsForNullConnection()
{
    try
    {
        ((SqlConnection)null).ExecuteDataSet("SELECT TOP 1 * FROM prep");
    }
    catch(ArgumentNullException e)
    {
        Assert.AreEqual(true, true);
    }
    catch (Exception e)
    {
        Assert.Fail("Expected ArgumentNullException, but {1} was thrown instead.", e.GetType().Name);
    }
}

回答1:

My guess is that you're not really testing the code you think you are. Try putting some Console.WriteLine statements in and see if they're printed. If you put a breakpoint on the throw statement and run the tests in the debugger, does the breakpoint get hit? If control is passing to the next statement, that means the exception is never being thrown - it can't possibly be caught in a way which would let execution continue in the throwing method, unless you've found a really weird CLR bug.

I've written lots of code like this and it's never failed in NUnit.

As an aside, I view it as good practice to include the parameter name in ArgumentExceptions, so I'd have written:

if (connection == null)
{
    throw new ArgumentNullException("connection");
}
if (sql == null)
{
    throw new ArgumentNullException("sql");
}

This has the unfortunate problem of being longwinded and repeating the parameter names in the code as strings... but it's reasonably easy to get right (especially with ReSharper helping to validate the names) and it could be really useful if this ever triggers in production. (There are some grotty hacks to validate the arguments in other ways, but I suspect you don't want to see those...)