Testing Routes for Web Api Controllers

In this post I will describe in detail how to perform integration tests on Web Api controllers, including routes and authentication. 

I am going to use the same Angular MVC application I used in many of my Angular posts.  In this application I use Structure Map dependency resolution, but the same testing approach will apply to whatever dependency resolver you want to use.  So, before I start on tests, I want to recap dependency resolution and routing.  I use NuGet package to install StructureMap, which generated the following file in my host project, which contains my MVC views, but also references the assembly with Web Api controllers.

[assembly: WebActivator.PreApplicationStartMethod(typeof(AngularAspNetMvc.Web.App_Start.StructuremapMvc), "Start")]

namespace AngularAspNetMvc.Web.App_Start {
    public static class StructuremapMvc {
        public static void Start() {
            IContainer container = IoC.Initialize();
            DependencyResolver.SetResolver(new StructureMapDependencyResolver(container));
            GlobalConfiguration.Configuration.DependencyResolver = new StructureMapDependencyResolver(container);
        }
    }
}

There is also IoC file, that is referenced in the code above, but I will omit it for brevity.  I also use AutoMapper, so I need to create my maps at the same time.

Now onto the tests.  I will cover two use cases here:  testing controllers directly and testing them through routing.  To test them directly, I have to implement the same dependency injection in my tests as I do in the main application.  As I do not want to repeat this code many time, I will implement a base class for my test classes.  I will call it ApiTest.  I will simply do this in the protected constructor, but I need to protect this code against being executed multiple times during single test run.  I will go simple route to achieve this, and just set static flag on my base class.

    public class ApiTest
    {
        private static bool _isSetup;
        private static readonly object Lock = new object();
        protected IContainer Container { get; private set; }
        protected ApiTest()
        {
            if (!_isSetup)
            {
                lock (Lock)
                {
                    if (!_isSetup)
                    {
                        StructuremapMvc.Start();
                        MappingConfiguration.CreateMaps();
                        _isSetup = true;
                    }
                }
            }
            Container = ObjectFactory.Container;
        }

This code is quite simple.  _isSetup is my flag to keep this code against running multiple times.  I also save the container into the class, because I will need it later.  You see that I am using it in GetInstance method.  I also create AutoMapper maps art the same time.

Now, I am ready to write direct controller test.  I am going to use ContactTypes controller for testing.

namespace AngularAspNetMvc.Tests.Core
{
    public class ApiTest
    {
        private static bool _isSetup;
        private static readonly object Lock = new object();
        protected IContainer Container { get; private set; }
        protected ApiTest()
        {
            if (!_isSetup)
            {
                lock (Lock)
                {
                    if (!_isSetup)
                    {
                        StructuremapMvc.Start();
                        MappingConfiguration.CreateMaps();
                        _isSetup = true;
                    }
                }
            }
            Container = ObjectFactory.Container;
        }


        protected T GetInstance<T>()
        {
            return Container.GetInstance<T>();
        }

I am going to test Save method. I could directly create the instance of the controller, but now I will show you why I saved off StructureMap container.  Instead of creating manually all dependency that my controller needs, I will use dependency injection via GetInstance<T>() method in my base test class as you can see above.  The rest of my code is quite simple:

        [TestMethod]
        public void Insert_Should_Add_Contact()
        {
            var contactType = new ContactType
            {
                Name = "Test Type"
            };

            var controller = GetInstance<ContactTypesController>();
            var result = controller.Save(contactType);
            Assert.IsTrue(result.Success, "Should successfully execute");
            Assert.IsTrue(result.Result.ContactTypeId > 0, "Should have generated id");
            contactType = result.Result;
            Assert.AreEqual(contactType.Name, result.Result.Name, "Should have correct name");

            if (contactType != null)
            {
                using (var context = new ContactsContext())
                {
                    var entity = Mapper.Map<Data.Models.ContactType>(contactType);
                    context.Entry(entity).State = EntityState.Deleted;
                    context.SaveChanges();
                }
            }
        }

Let’s walk through this code.  I create a ContactType instance and set the required properties.  Then I get an instance of the controller I am testing, using StructureMap to satisfy dependencies, repository in my case.  Then I just call Save method directly.  Then I test the results of the save method.  Finally I clean up test data from the database.  Very easy so far.  More complex tests are also possible, you just need to create all the data they need.  Now, I want to test the same thing through entire Web Api infrastructure.  I am going to use in memory implementation of the Web Api infrastructure, called HttpServer.  I do not need System.Web to write those tests and I do not need to host them inside IIS, which makes it much easier to test, even write integration tests like you see below.  In this test, I am doing exact same thing as the test above.

 [TestMethod]
        public void Insert_Should_Add_Contact_Via_Route()
        {
            ContactType contactType = null;
            using (var client = PrepareServer())
            {
                using (var request = new HttpRequestMessage(HttpMethod.Post, "http://localhost/api/contacttypes/save"))
                {

                    AddAuthorizationCookie("me@you.com", new { Prop1 = "1", Prop2 = "2" }, request);
                    var type = new ContactType
                    {
                        Name = "Test Type"
                    };
                    request.Content = new ObjectContent(typeof(ContactType), type, new JsonMediaTypeFormatter());
                    using (HttpResponseMessage response = client.SendAsync(request, CancellationToken.None).Result)
                    {

                        var content = response.Content.ReadAsStringAsync().Result;
                        var result = JsonConvert.DeserializeObject<ApiResult<ContactType>>(content);
                        Assert.IsTrue(result.Success, "Should successfully execute");
                        Assert.IsTrue(result.Result.ContactTypeId > 0, "Should have generated id");
                        contactType = result.Result;
                        Assert.AreEqual(type.Name, result.Result.Name, "Should have correct name");
                    }
                }
            }

            if (contactType != null)
            {
                using (var context = new ContactsContext())
                {
                    var entity = Mapper.Map<Data.Models.ContactType>(contactType);
                    context.Entry(entity).State = EntityState.Deleted;
                    context.SaveChanges();
                }
            }
        }

In the code above I am doing the following.  I am preparing HttpServer.  Then I am creating HttpMessage to send to the server.  Then I am adding authentication cookie.  In the next step I am creating an instance of ContactType and I am serializing it into the message content.  Finally, I am sending the message to the server.  In the last two steps I test the result and clean up. Let me show you what I do in my base test class.  First of all, I need to create HttpServer, satisfying all the configuration. 

        protected HttpMessageInvoker PrepareServer()
        {

            var server = new HttpServer(CreateHttpConfiguration());
            return new HttpMessageInvoker(server);
        }

        protected HttpConfiguration CreateHttpConfiguration()
        {
            var config = new HttpConfiguration();
            WebApiConfig.Register(config);
            config.IncludeErrorDetailPolicy = IncludeErrorDetailPolicy.Always;
            config.DependencyResolver = new TestStructureMapResolver(ObjectFactory.Container);
            return config;
        }

As you can see, creating server is very easy, the hardest part is to configure routes and setup dependency injection.  I do not need to setup AutoMapper maps, this is done in the test class constructor already, since it is all run in the same memory space.  I also set IncludeErrorDetailsPolicy in order to ease debugging.  Finally, I use test resolver that does not dispose the scope.  If you do, then you cannot run multiple tests in the same run, since the scope dispose method will dispose StrucureMap container.

Finally, here is how I add Forms Authentication cookie.

        protected void AddAuthorizationCookie(string userId, object userData, HttpRequestMessage message)
        {

            var ticket = new FormsAuthenticationTicket(
                    1,
                    userId,
                    DateTime.UtcNow,
                    DateTime.UtcNow.AddHours(1),
                    false,
                    JsonConvert.SerializeObject(userData));

            string encryptedTicket = FormsAuthentication.Encrypt(ticket);

            message.Headers.Add("Cookie", new[]
            {
                FormsAuthentication.FormsCookieName + "=" + encryptedTicket + "; path=" + FormsAuthentication.FormsCookiePath
            });
        }

You can also inject other headers the same way.

You can download entire project here.

Enjoy.

Leave a Reply

Your email address will not be published. Required fields are marked *