Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How come you cannot catch Code Contract exceptions?

System.Diagnostics.Contracts.ContractException is not accessible in my test project. Note this code is purely myself messing around with my shiney new copy of Visual Studio, but I'd like to know what I'm doing wrong.

I'm using the professional edition of VS, therefore I do not have static checking. In order to still use code contracts (which I like) I figured the only way my method can work is to catch the exception that is thrown at runtime, but I'm not finding this possible.

TestMethod

[TestMethod, ExpectedException(typeof(System.Diagnostics.Contracts.ContractException))]
public void returning_a_value_less_than_one_throws_exception()
{
    var person = new Person();
    person.Number();
}

Method

public int Number()
{
    Contract.Ensures(Contract.Result<int>() >= 0);
    return -1;
}

Error

Error 1 'System.Diagnostics.Contracts.ContractException' is inaccessible
due to its protection level.

Edit

After some more thought I've come to the conclusion discussed in the comments, as well as the following. Given a method, if this had a requirement which could be expressed in Code Contract form, I'd write tests as such.

[TestMethod]
[ExpectedException(typeof(ArgumentException))]
public void value_input_must_be_greater_than_zero()
{
    // Arrange
    var person = new Person();
    // Act
    person.Number(-1);
}

This would ensure the contract is part of the code, and will not be removed. This would require the Code Contract to actually throw the specified exception however. In some cases this would not be required however.

like image 441
Finglas Avatar asked Apr 14 '10 18:04

Finglas


3 Answers

This is deliberate - although a slight pain for testing.

The point is that in production code you should never want to catch a contract exception; it indicates a bug in your code, so you shouldn't be expecting that any more than arbitrary unexpected exceptions which you may want to catch right at the top of your call stack so you can move onto the next request. Basically you shouldn't view contract exceptions as ones which can be "handled" as such.

Now, for testing that's a pain... but do you really want to test your contracts anyway? Isn't that a bit like testing that the compiler stops you from passing in a string to a method which has an int parameter? You've declared the contract, it can be documented appropriately, and enforced appropriately (based on settings, anyway).

If you do want to test contract exceptions, you can either catch a bare Exception in the test and check its full name, or you can mess around with the Contract.ContractFailed event. I would expect unit testing frameworks to have built-in support for this over time - but it'll take a little while to get there. In the meantime you probably want to have a utility method to expect a contract violation. One possible implementation:

const string ContractExceptionName =
    "System.Diagnostics.Contracts.__ContractsRuntime.ContractException";

public static void ExpectContractFailure(Action action)
{
    try
    {
        action();
        Assert.Fail("Expected contract failure");
    }
    catch (Exception e)
    {
        if (e.GetType().FullName != ContractExceptionName)
        {
            throw;
        }
        // Correct exception was thrown. Fine.
    }
}
like image 95
Jon Skeet Avatar answered Nov 14 '22 10:11

Jon Skeet


EDIT: I have had a conversion and no longer use either ExpectedException or this attribute below, but rather have coded some extension methods:

AssertEx.Throws<T>(Action action);
AssertEx.ThrowsExact<T>(Action action);
AssertEx.ContractFailure(Action action);

These allow me to be more precise about where the exception is raised.

Example of ContractFailure method:

    [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "Cannot catch ContractException")]
    public static void ContractFailure(Action operation)
    {
        try
        {
            operation();
        }
        catch (Exception ex)
        {
            if (ex.GetType().FullName == "System.Diagnostics.Contracts.__ContractsRuntime+ContractException")
                return;

            throw;
        }

        Assert.Fail("Operation did not result in a code contract failure");
    }

I created an attribute for MSTest, that behaves similarly to the ExpectedExceptionAttribute:

public sealed class ExpectContractFailureAttribute : ExpectedExceptionBaseAttribute
{
    const string ContractExceptionName = "System.Diagnostics.Contracts.__ContractsRuntime+ContractException";

    protected override void Verify(Exception exception)
    {
        if (exception.GetType().FullName != ContractExceptionName)
        {
            base.RethrowIfAssertException(exception);
            throw new Exception(
                string.Format(
                    CultureInfo.InvariantCulture,
                    "Test method {0}.{1} threw exception {2}, but contract exception was expected. Exception message: {3}",
                    base.TestContext.FullyQualifiedTestClassName,
                    base.TestContext.TestName,
                    exception.GetType().FullName,
                    exception.Message
                )
            );
        }
    }
}

And this can be used similarly:

    [TestMethod, ExpectContractFailure]
    public void Test_Constructor2_NullArg()
    {
        IEnumerable arg = null;

        MyClass mc = new MyClass(arg);
    }
like image 24
Stephen Drew Avatar answered Nov 14 '22 12:11

Stephen Drew


in the vs2010 rtm, the full name has been changed to "System.Diagnostics.Contracts.__ContractsRuntime+ContractException". HTH

like image 2
user341451 Avatar answered Nov 14 '22 10:11

user341451