Testing NancyFx



Hi, I've recently been getting quite into NancyFx. I've written a quickstart NancyFx tutorial if you've interested and even a NancyFx screencast.

Next I thought I'd do a very short followup on testing in NancyFx.

Before you start

Download Nancy Tutorial with testing source code from GitHub

Step 1 - Create a clean NancyBootstrapper

In the previous NancyFx tutorial, I hastily advocated overriding the ApplicationStartup and subscribing to the Error pipeline. Unfortunately this was also mixed in with the StructureMap Bootstrapper. So to seperate those concerns and make it so we can easily inject a Stub NancyBootstrapper.

Your new NancyBootstrapper should look like this:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Web;
using Nancy;
using Nancy.Bootstrappers.StructureMap;

namespace NancyTutorial
{
    public class NancyBootstrapper : StructureMapNancyBootstrapper
    {
        protected override void ConfigureApplicationContainer(StructureMap.IContainer existingContainer)
        {
            StructureMapContainer.Configure(existingContainer);
        }

    }
}

Step 2 - Create a class that implements IApplicationStartup

Next we're going to create a CarNotFoundExceptionErrorPipeline this class will simply hook the error pipeline and and some code to check whether the exception is of type CarNotFound and if so it'll return a 404 Not Found Error.

It should look like this:

using System.Text;
using Nancy;
using Nancy.Bootstrapper;

namespace NancyTutorial
{
    public class CarNotFoundExceptionErrorPipeline : IApplicationStartup
    {
        public void Initialize(IPipelines pipelines)
        {
            pipelines.OnError += (context, exception) =>
            {
                if (exception is CarNotFoundException)
                    return new Response
                    {
                        StatusCode = HttpStatusCode.NotFound,
                        ContentType = "text/html",
                        Contents = (stream) =>
                        {
                            var errorMessage =
                                Encoding.UTF8.GetBytes(
                                    exception.Message);
                            stream.Write(errorMessage, 0,
                                         errorMessage.Length);
                        }
                    };

                return HttpStatusCode.InternalServerError;
            };
        }
    }
}

Step 3 - Create a Tests Project

Now we simply need to create a Test project class library. Mines very originally called NancyTutorial.Web.Tests

NancyFx comes with a really nice, simple and best of all fast module called Nancy.Testing this does away with a lot of the need to use WebClient or WebRequest. Which makes life considerably easier, hopefully it'll help make your testing easier to read and thus more maintainable.

Step 4 - Create a simple Test using Nancy.Testing

Since we've only got 1 module in our project called CarModule I'm going to follow the lead and call the test class CarModuleTests. As the Nuget picture above shows we'll also be using trusty old NUnit and Moq to help us with testing our module.

Our first test will exercise the /car/123 endpoint. When we call it we'll expect back an Xml representation of a car, because we will explicity tell the server via the accept headers that we want application/xml. Nancy.Testing makes this really easy for us.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Moq;
using NUnit.Framework;
using Nancy;
using Nancy.Testing;

namespace NancyTutorial.Web.Tests
{
    [TestFixture]
    public class CarModuleTests
    {
        [Test]
        public void Should_return_car_as_xml_when_present()
        {
            //Given
            var mockRepository = new Mock<ICarRepository>();
            mockRepository.Setup(x => x.GetById(123)).Returns(new Car
                                                                  {
                                                                      Id = 123,
                                                                      Make = "Tesla",
                                                                      Model = "Model S"
                                                                  });           
            var browser = new Browser((c) => c.Module<CarModule>()
                                                 .Dependency<ICarRepository>(mockRepository.Object));
            //When
            var response = browser.Get("/car/123", with =>
                                                       {
                                                           with.HttpRequest();
                                                           with.Header("accept", "application/xml");
                                                       });
            var responseBody = response.BodyAsXml();
            //Then
            Assert.That(response,Is.Not.Null);
            
            string make = responseBody.Element("Car").Element("Make").Value;

            Assert.That(make,Is.EqualTo("Tesla"));
        }

      
    }
}

Step 5 - Mock the repository

So if we look at the code above, we firstly decide to mock an ICarRepository this is because we want to control what the repository hands back since we don't want this test talking to a real database (Not that this example even has one). We setup an expectation that the repository will have it's GetCarById(123) method called with an id of 123 and as you can see we hard code the return of a Tesla Model S with Id of 123.

Step 6 - Crete a Nancy testing Browser

Now this is where it gets cool. We create a Browser type (which is from Nancy testing and we pass it a lambda that is equivilent to a ConfigurableBootstrapper. All we do is for the CarModule with it's dependency on ICarRepository that we use a mock repository instead of the real one which would be resovled had we used the "real" NancyBootsrapper class.

Step 7 - call browser.Get()

Looking at the code again, you can see we call the browser.Get("/url") method. Nancy testing of course has all the relevant verbs such as browser.Post and browser.Delete. The second parameter is the interesting bit. It's another lambda. It lets us control properties of the requesting Nancy browser. For example above you see we've set this request to be over Http via the with.HttpRequest()  and helpfully we've hinted to the server that we can accept application/xml and we therefore will expect a response in xml.

There's another really helpful one call with.Query() which is how you put items on the querystring. Don't try just putting them in the get, it won't work, because that's todo with Nancy routing as far as Nancy testings concerned.

Step 8 - Assert away

We can also see that there's a multitude of handy extension methods such as the used above response.BodyAsXml() which takes the body response and gives us an XDocument in return.

Step 9 - Testing the response when we throw an exception

Next we'll write a simple test to check that, the above code whereby we moved the error handling code into a new class called CarNotFoundExceptionErrorPipeline is actually working.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Moq;
using NUnit.Framework;
using Nancy;
using Nancy.Testing;

namespace NancyTutorial.Web.Tests
{
    [TestFixture]
    public class CarModuleTests
    {
        [Test]
        public void Should_return_car_as_xml_when_present()
        {
            //Given
            var mockRepository = new Mock();
            mockRepository.Setup(x => x.GetById(123)).Returns(new Car
                                                                  {
                                                                      Id = 123,
                                                                      Make = "Tesla",
                                                                      Model = "Model S"
                                                                  });           
            var browser = new Browser((c) => c.Module()
                                                 .Dependency(mockRepository.Object));
            //When
            var response = browser.Get("/car/123", with =>
                                                       {
                                                           with.HttpRequest();
                                                           with.Header("accept", "application/xml");
                                                       });
            var responseBody = response.BodyAsXml();
            //Then
            Assert.That(response,Is.Not.Null);
            
            string make = responseBody.Element("Car").Element("Make").Value;

            Assert.That(make,Is.EqualTo("Tesla"));
        }

        [Test]
        public void Should_return_404_NotFound_when_CarRespository_throws_NotFoundException()
        {
            //Given
            var mockRepository = new Mock();
            mockRepository.Setup(x => x.GetById(111)).Throws(new CarNotFoundException("Car with Id 111 Not Found"));

            var browser = new Browser((c) => c.Module<CarModule>()
                                                 .Dependency<ICarRepository>(mockRepository.Object));
            //When            
            var response = browser.Get("/car/111", with =>
                                                       {
                                                           with.HttpRequest();
                                                           with.Header("accept", "application/xml");
                                                       });

            var responseBody = response.Body.AsString();
            //Then
            Assert.That(response.StatusCode,Is.EqualTo(HttpStatusCode.NotFound));
            Assert.That(responseBody, Is.EqualTo("Car with Id 111 Not Found"));



        }
    }
}

If we look at the 2nd test listed above  Should_return_404_NotFound_when_CarRespository_throws_NotFoundException() we can see that it's much the same as the first but with some simple modifications.

Firstly instead of returning a car we hard code the repository to throw a CarNotFoundException we then use the ConfigurableNancyBootsrapper lambda to resolve the CarModule dependency on ICarRepository to our mocked repository. combined with our mocked throwing of the exception we call Get on the enpoint.

Not we use another handy extension method response.Body.AsString() to convert the response into a simple string. We then assert that the code is correctly functioning by checking the http status code is indeed 404 Not Found and that the error message is correct.

Conclusion

That's it for a brief introduction to Nancy testing. As you can see it's really ncie and simple.

References

I highly recommend checking out the Nancy Testing Documentation for a more Nancy testing examples.

Thanks for reading Dan


blog comments powered by Disqus

Search

Disclaimer

The opinions expressed herein are my own personal opinions and do not represent my employer's view in anyway.

© Copyright 2014