cbp
cbp

Reputation: 25628

Testing Web API routes with MvcRouteTester

I am using the MVC 4 version of Anthony Steele's MVC Route Tester, which seems to be the best offering for testing MVC routes.

I am trying to get the following Web API test to work, but it's not happening. Can someone help me out:

var config = new HttpConfiguration();

config.Routes.MapHttpRoute(
    name: "Testing",
    routeTemplate: "WebServices/Test.aspx",
    defaults: new
    {
        controller = "Test" // This maps to an ApiController called TestController
    }
);

config
    .ShouldMap("/WebServices/Test.aspx")
    .To<TestController>(HttpMethod.Get, x => x.Get());

This test fails with error MvcRouteTester.Assertions.AssertionException : Route with controller not found for url http : // site.com/WebServices/Test.aspx.

The route works when I actually run the site, just not in my test project. What could be wrong?

Upvotes: 2

Views: 502

Answers (1)

cbp
cbp

Reputation: 25628

I've figured out the issue after digging through the code. Deep within the stack there is a call to get the 'exportable types' from my website's assembly. In production the call succeeds, but because my unit test project is missing a couple of assemblies references, the call to Assembly.GetExportableTypes() throws an exception. This exception is caught and discard, so it was very difficult to debug the reason why I was getting a 'controller not found' exception later on.

It is possible to fix the issue by making sure you have added all the required assemblies. But if you want to make life easier for yourself you can actually replace the IHttpControllerTypeResolver service that is suppressing the exception. Here is an example re-implementation of IHttpControllerTypeResolver.

public class HttpControllerTypeResolverThatDoesNotIgnoreExceptionGettingLoadableTypes : DefaultHttpControllerTypeResolver
{
    public override ICollection<Type> GetControllerTypes(IAssembliesResolver assembliesResolver)
    {
        if (assembliesResolver == null)
        {
            throw new Exception();
        }

        var result = new List<Type>();

        // Go through all assemblies referenced by the application and search for types matching a predicate
        ICollection<Assembly> assemblies = assembliesResolver.GetAssemblies();
        foreach (Assembly assembly in assemblies)
        {
            Type[] exportedTypes = null;
            if (assembly == null || assembly.IsDynamic)
            {
                // can't call GetExportedTypes on a dynamic assembly
                continue;
            }

            try
            {
                exportedTypes = assembly.GetExportedTypes();
            }
            catch (ReflectionTypeLoadException ex)
            {
                exportedTypes = ex.Types;
                Trace.TraceError(ex.ToString());
            }
            catch (Exception ex)
            {
                Trace.TraceError(ex.ToString());
                continue;
            }

            if (exportedTypes != null)
            {
                result.AddRange(exportedTypes.Where(IsControllerType));
            }
        }

        return result;
    }

    private static bool IsControllerType(Type t)
    {
        return
            t != null &&
            t.IsClass &&
            t.IsPublic &&
            t.Name.EndsWith(DefaultHttpControllerSelector.ControllerSuffix, StringComparison.OrdinalIgnoreCase) &&
            !t.IsAbstract &&
            typeof(IHttpController).IsAssignableFrom(t);
    }
}

You can hookup this service to your configuration like this:

_yourHttpConfig.Services.Replace(typeof(IHttpControllerTypeResolver), new HttpControllerTypeResolverThatDoesNotIgnoreExceptionGettingLoadableTypes());

The above implementation will output any exceptions occurring to the trace, which may help with debugging. Alternatively you could use a method such as this one in order to skip over any of the individual exportable types that couldn't be successfully loaded but still get the rest of the successful exportable types.

Upvotes: 1

Related Questions