Question

I'm just setting up some first unit tests, and I can't quite see how I'm trying to achieve (with my current test structure) can be done, which means I'm not sure whether my approach to the tests is incorrect, or it's just a limitation on xUnit.

I'm testing my MVC Controllers, and want to ensure that they all provide a ArgumentNullException if they are constructed passing null across as the arguments (they get resolved via Castle in the real world).

So, I've a private field on the Test class:

private IEnumerable<Type> ControllerTypes = typeof(MyBaseController).Assembly.GetTypes().Where(t => IsController(t));

Then, my test method:

    [Fact]
    public void EnsureControllersThrowIfProvidedWithNull() {
        foreach (var controller in ControllerTypes) {
            var ctrs = GetConstructorsForType(controller);
            if (null == ctrs || !ctrs.Any()) { //if the controller has no constructors, that's fine, we just skip over it
                continue;
            }
            var ctr = ctrs.ElementAt(0);
            var ctrParamsAsNull = ctr.GetParameters().Select(p => (object)null);
            Assert.Throws<ArgumentNullException>(() => {
                ctr.Invoke(ctrParamsAsNull.ToArray());
            });
        }
    }

So this is all working fine, I run the test runner, and one of my Controllers doesn't throw an ArgumentNullException when passed null, great, my test fails, but I don't know which controller it was, from the given output.

I do know how I can debug through the test to see which it is that fails, and can manually go through all my controllers to check which it is, but it would be useful to know which controller it was that failed.

Or am I just using a unit test wrong here?

(Side note, there's another test which ensures there's only 1 public constructor for each controller, so I can be sure I'm targeting the correct constructor when this fires, as long as that first test passed).

Thanks

Note: There's a flaw in the logic for the test, which means it doesn't fully cover what I was expecting it too, as long as it throws an ArgumentNullException for at least 1 of the arguments, then it will pass the test, which isn't right. However as the arguments are interfaces I can't instantiate a new instance of them. So anyone looking to copy the code for the test, I wouldn't do so. Not looking for a solution to that issue here.

Was it helpful?

Solution

Assert.Throws is only helper method that executes delegate inside try catch block. You don't have to use it and you can replace it with your own implementation. Something like:

[Fact]
public void EnsureControllersThrowIfProvidedWithNull() {
    foreach (var controller in ControllerTypes) {
        var ctrs = GetConstructorsForType(controller);
        if (null == ctrs || !ctrs.Any()) { //if the controller has no constructors, that's fine, we just skip over it
            continue;
        }
        var ctr = ctrs.ElementAt(0);
        var ctrParamsAsNull = ctr.GetParameters().Select(p => (object)null);
        book ok = false;
        try
        {
            ctr.Invoke(ctrParamsAsNull.ToArray());
        }
       catch(ArgumentNullException)
       {
            //you get exception you expected so continue
            ok = true;
       }
       if(!ok)
       {
          // you didn't get exception so throw your own exception with message that contains controller type name 
           throw new Exception(String.Format("Ctor on type {0} did not throw ArgumentNullException",controller.Name);
       }
    }
}

This is only as idea to work on. You can refactor that inside your own static assertion method...

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top