left-icon

NancyFX Succinctly®
by Peter Shaw

Previous
Chapter

of
A
A
A

CHAPTER 14

Testing

Testing


One of the real hidden gems when it comes to Nancy is the testing framework provided with it. Nancy was built from the word “go” with the ability to test it in mind. Because of this, the super-duper-happy-path extends not only to developing applications with it, but to testing applications too.

Like everything else you can add to Nancy, you can get the testing framework using NuGet, by installing Nancy.Testing.

In order to take a closer look at how to use the testing framework, add a new class library project to your existing solution. You can create the tests as part of your actual application if you wish, but keeping them in a separate assembly is both recommended and sane.

Adding a new class library project to put our tests in

Figure 31: Adding a new class library project to put our tests in

Once you have added your new class library project, pull up your NuGet Package Manager and add Nancy.Testing to the new project:

Adding the Nancy testing package

Figure 32: Adding the Nancy testing package

While you're in your Package Manager, add the NUnit testing framework (or whichever test framework you prefer to use) to the project. I Use NUnit because I'm a JetBrains ReSharper user, and R# has built-in support for NUnit by default, allowing me to easily manage my tests.

The tests I write in this chapter to demo Nancy's testing abilities will be written using NUnit's syntax. If you use a different test framework, you'll need to alter your tests so that they use the syntax your framework and test runner support.

Add a new class to your test project and add in the following code:

Code Listing 68

using nancybook.modules;

using Nancy;

using Nancy.Testing;

using NUnit.Framework;

namespace NancyTesting

{

  class GetTest

  {

    [Test]

    public void SimpleTestOne()

    {

      // Arrange

      var browser = new Browser(with => with.Module(new TestingRoutes()));

      // Act

      var response = browser.Get("/");

      // Assert

      Assert.AreEqual(HttpStatusCode.OK, response.StatusCode);

     

    }

  }

}

You'll see that TestingRoutes is in red, and if you run your test at this point, it will fail to build.

Add a project reference to your main Nancy-based application to the testing project, and then in your main Nancy application, add an empty route module called TestingRoutes.cs as follows:

Code Listing 69

using System;

using System.Collections.Generic;

using System.IO;

using Nancy;

using Nancy.Responses;

namespace nancybook.modules

{

  public class TestingRoutes : NancyModule

  {

  }

}

Update your test to include the namespace where your new module lives. In my case, I added using nancybook.modules; to the using clause of my test assembly.

At this point, the TestingRoutes reference should change from red to your default class name color, indicating that it can be referenced.

If you try running your test now, you should find that the test compiles and runs. It still fails, however, as we've not yet implemented the route we’re testing for.

Our first test failing

Figure 33: Our first test failing

Go back to your main Nancy application, and update the testing routes module so that it looks like this:

using Nancy;

namespace nancybook.modules

{

  public class TestingRoutes : NancyModule

  {

    public TestingRoutes()

    {

      Get[@"/"] = _ => "Hello World";

    }

  }

}

Run your test again, and this time it should pass.

This time our test passes

Figure 34: This time our test passes

Nancy provides you with a Browser object. This object is not an abstraction of the browser in your system; its a custom, self-running headless browser instance that's able to perform many of the things your browser can do, only in isolation.

For example, you can test your post acceptors very easily by asking Nancy to send form values to your routes and examine the response returned. Let's imagine we have a route that allows us to store a name and email address in the database, and we want that route to provide a status code of 200 if the record was saved, 409 if it already existed, or 500 for any other error.

To do this, we'd most likely create three tests as follows:

Code Listing 70

    [Test]

    public void PostTest_Should_Return_200()

    {

      // Arrange

      var browser = new Browser(with => with.Module(new TestingRoutes()));

      // Act

      var response = browser.Post("/save/", (with) =>

      {

        with.HttpRequest();

        with.FormValue("Name", "Joe Smith");

        with.FormValue("Email", "[email protected]");

      });

      // Assert

      Assert.AreEqual(HttpStatusCode.OK, response.StatusCode)

    }

    [Test]

    public void PostTest_Should_Return_409()

    {

      // Arrange

      var browser = new Browser(with => with.Module(new TestingRoutes()));

      // Act

      var response = browser.Post("/save/", (with) =>

      {

        with.HttpRequest();

        with.FormValue("Name", "Existing Person");

        with.FormValue("Email", "[email protected]");

      });

      // Assert

      Assert.AreEqual(HttpStatusCode.Conflict, response.StatusCode);

    }

    [Test]

    public void PostTest_Should_Return_500()

    {

      // Arrange

      var browser = new Browser(with => with.Module(new TestingRoutes()));

      // Act

      var response = browser.Post("/save/", (with) =>

      {

        with.HttpRequest();

        with.FormValue("NOTName", "Existing Person");

        with.FormValue("NOTEmail", "[email protected]");

      });

      // Assert

      Assert.AreEqual(HttpStatusCode.InternalServerError, response.StatusCode);

    }

If you run these immediately, you'll see that they fail, as expected.

All our post tests fail

Figure 35: All our post tests fail

Let's switch back to our testing routes module in the main application and check for our parameters; if they don't exist, we'll return a 500 error.

Code Listing 71

using Nancy;

namespace nancybook.modules

{

  public class TestingRoutes : NancyModule

  {

    public TestingRoutes()

    {

      Get[@"/"] = _ => "Hello World";

      Post[@"/save"] = _ =>

      {

        if (!Request.Form.Name.HasValue || !Request.Form.Email.HasValue)

        {

          return 500;

        }

        return null;

      };

    }

  }

}

Rerun your tests, and all being well, the 500 one should pass, but the other two should still fail. You might actually find that the 200 test passes too; even though we returned a null, Nancy will still see that as a successful test and pass a 200 ok by default. If it does, don't worry—for our test purposes, it's perfectly fine.

Let's make our test actually pass the other two tests as expected. Change your route module so that the code now looks like this:

Code Listing 72

using Nancy;

namespace nancybook.modules

{

  public class TestingRoutes : NancyModule

  {

    public TestingRoutes()

    {

      Get[@"/"] = _ => "Hello World";

      Post[@"/save"] = _ =>

      {

        if (!Request.Form.Name.HasValue || !Request.Form.Email.HasValue)

        {

          return HttpStatusCode.InternalServerError;

        }

        if (Request.Form.Name == "Existing Person" && Request.Form.Email == "[email protected]")

        {

          return HttpStatusCode.Conflict;

        }

        return HttpStatusCode.OK;

      };

    }

  }

}

This time, all three tests should pass.

You can also check for other results, such as redirects. Let's imagine that instead of returning a 500, our route module returned a redirect to an error page. We can test for this by changing the test as follows:

Code Listing 73

    [Test]

    public void PostTest_Should_Return_500()

    {

      // Arrange

      var browser = new Browser(with => with.Module(new TestingRoutes()));

      // Act

      var response = browser.Post("/save/", (with) =>

      {

        with.HttpRequest();

        with.FormValue("NOTName", "Existing Person");

        with.FormValue("NOTEmail", "[email protected]");

      });

      // Assert

      response.ShouldHaveRedirectedTo("/posterror");

    }

Running it without changing the route should once again fail. However, we can change the route so that it performs a redirect as follows:

Code Listing 74

using Nancy;

namespace nancybook.modules

{

  public class TestingRoutes : NancyModule

  {

    public TestingRoutes()

    {

      Get[@"/"] = _ => "Hello World";

      Post[@"/save"] = _ =>

      {

        if (!Request.Form.Name.HasValue || !Request.Form.Email.HasValue)

        {

          return Response.AsRedirect("/posterror");

        }

        if (Request.Form.Name == "Existing Person" && Request.Form.Email == "[email protected]")

        {

          return HttpStatusCode.Conflict;

        }

        return HttpStatusCode.OK;

      };

    }

  }

}

This will once again cause the test to pass.

Nancy's testing features can also check the returned HTML content for given elements, classes, and IDs; in fact, if you can create a CSS3 style selector for it, then you can test for it.

The test classes use CsQuery under the hood for this, and speaking from experience, I've yet to find a selector it's not been able to handle.

Here’s an example from the NancyFX Wiki: if you wanted to submit an incorrect login to a login form, then check that the output contained an HTML element called errorBox, with a certain CSS class, you could use the following test to do this easily:

Code Listing 75

    [Test]

    public void Should_display_error_message_when_error_passed()

    {

      // Given

      var bootstrapper = new DefaultNancyBootstrapper();

      var browser = new Browser(bootstrapper);

      // When

      var response = browser.Get("/login", (with) =>

      {

        with.HttpRequest();

        with.Query("error", "true");

      });

      // Then

      response.Body["#errorBox"]

              .ShouldExistOnce()

              .And.ShouldBeOfClass("floatingError")

              .And.ShouldContain(

                  "invalid",

                  StringComparison.InvariantCultureIgnoreCase);

    }

You'll notice in that example that we created the browser using a default Nancy bootstrapper. You can modify this bootstrapper in exactly the same way as you modify your custom bootstrappers in the main application. You can also use it to configure the IoC container to further aid you in your testing efforts.

Summary

In this chapter, you saw much more of the super-duper-happy-path. You saw how easy Nancy makes it for you to test your Nancy applications, and how it solves one of the biggest headaches that ALL web developers face: testing the content of the HTML that makes up user interfaces.

Over the last 100 pages, you've discovered (I hope) that Nancy once again brings joy to developing web-based services. When I was discussing this with some of my peers, getting ideas for what to include in the book, one of them said something to me that's stuck with me since I started writing it: "NancyFX? Isn’t that the toolkit that tries to be NodeJS for C#?"

NancyFX is much more, in my opinion, than NodeJS can ever be (although Node does come a very close second). NancyFX represents a framework that has been built by developers who have gone through all the pain that the rest of us have, and have come out of the other side bearing the scars.

The next time you’re browsing around on Twitter, remember to ping a message to @TheCodeJunkie and @GrumpyDev thanking them for making your life and web development efforts so much easier. If you'd like to participate in the project and possibly become a Nancy MVM (Most Valued Minion) then hop over to NancyFX.org and read the docs.

There's so more you can do with the framework—we’ve reached the end of our journey in this book, but your journey with Nancy has just begun.

Enjoy the "Super-Duper-Happy-Path" to web application development.

Figure 36: Enjoy the "Super-Duper-Happy-Path" to web application development.

Scroll To Top
Disclaimer
DISCLAIMER: Web reader is currently in beta. Please report any issues through our support system. PDF and Kindle format files are also available for download.

Previous

Next



You are one step away from downloading ebooks from the Succinctly® series premier collection!
A confirmation has been sent to your email address. Please check and confirm your email subscription to complete the download.