Usage Instructions

Examples

Recommended class structure

The following code snippet is an example for how to set up a test class to use FluentMVCTesting using NUnit. This is simply to get you started quickly; in reality you can use it how you like and with any unit testing framework of your choice.

using MyApp.Controllers;
using NUnit.Framework;
using TestStack.FluentMVCTesting;

namespace MyApp.Tests.Controllers
{
    [TestFixture]
    class HomeControllerShould
    {
        private HomeController _controller;

        [SetUp]
        public void Setup()
        {
            _controller = new HomeController();
        }

        [Test]
        public void Render_default_view_for_get_to_index()
        {
            _controller.WithCallTo(c => c.Index()).ShouldRenderDefaultView();
        }
    }
}

This checks that when _controller.Index() is called then the ActionResult that is returned is of type ViewResult and that the view name returned is either "Index" or "" (the default view for the Index action); easy huh?

Basic syntax

As you might have gathered from the previous code snippet the entry point to the library is the WithCallTo extension method on the MVC Controller class; inside of that method call you simply pass in a lamdba expression that takes the controller to make the action call that you are trying to test. From there you can use intellisense to guide your testing.

Model binding errors

If you want to test what happens when the default model binding (or indeed any custom model binding or validation that you perform) has left errors in ModelState when your action is called you can simply use the WithModelErrors() extension method, e.g.:

_controller.WithModelErrors().WithCallTo(c => c.Index()).ShouldRenderDefaultView();

Child actions

If you want to check that the action being called has the [ChildActionOnly] attribute then use WithCallToChild() rather than WithCallTo(), e.g.:

_controller.WithCallToChild(c => c.Index()).ShouldRenderDefaultView();

Empty result

_controller.WithCallTo(c => c.Index()).ShouldReturnEmptyResult();

Redirect to url

_controller.WithCallTo(c => c.Index()).ShouldRedirectTo("http://www.google.com.au/");

Redirect to route name

_controller.WithCallTo(c => c.Index()).ShouldRedirectToRoute("RouteName");

Redirect to action within the same controller

There are a number of ways that you can specify this test, depending on the signature of the action you are redirecting to.

If you are redirecting to an action that takes no parameters, or takes a single int parameter, then you can use a method group, which is the tersest specification (you don't need to specify the parameters to the action), e.g. if these are two actions in the controller you are testing:

public ActionResult ActionWithNoParameters()
{
    return new EmptyResult();
}
public ActionResult RedirectToActionWithNoParameters()
{
    return RedirectToAction("ActionWithNoParameters");
}

Then you can write this test:

_controller.WithCallTo(c => c.RedirectToActionWithNoParameters())
    .ShouldRedirectTo(c => c.ActionWithNoParameters);

We can explicitly define whatever signatures we want to allow this terser syntax, but the different permutations that are possible are infinite and not possible for any custom types in your project. If anyone has ideas for how to do this better let us know!

If you have 1-3 parameters being passed into the action being redirected to then you can still use the method group, but you need to specify the types being passed into the action, e.g. if you have the following controller action being redirected to:

public ActionResult SomeAction(string param1, int param2, bool param3) {}

Then you can write the following test for the redirect:

_controller.WithCallTo(c => c.Index())
    .ShouldRedirectTo<string, int, bool>(c => c.SomeAction);

If you have more than three parameters, or you are uncomfortable with that syntax then you can specify a lambda with a call to the action you want and pass in dummy values for the parameters, e.g. for the previous example:

_controller.WithCallTo(c => c.Index())
    .ShouldRedirectTo(c => c.SomeAction(null, 0, false));

You can also pass through a MethodInfo object against the method you are redirecting to, e.g.:

_controller.WithCallTo(c => c.Index())
    .ShouldRedirectTo(typeof(HomeController).GetMethod("SomeAction"));

We don't recommend using this latter option because it uses a "magic" string so if you change the action name then the string won't change and the test will break and also need to be updated. In saying that, if you change your parameters more often than the action name this might be a better option. If you do use this option be careful that you don't get an AmbiguousMatchException if there are multiple actions with the same name.

At this stage there isn't support for the [ActionName()] attribute or simply passing through a string to check against the action name, but if either are important to you feel free to add an issue in the GitHub project.

Redirect to action in another controller

If you are redirecting to an action in another controller, then there are two syntaxes that you can currently use (similar to the last two mentioned above):

_controller.WithCallTo(c => c.Index())
    .ShouldRedirectTo<SomeOtherController>(c2 => c2.SomeAction());

_controller.WithCallTo(c => c.Index())
    .ShouldRedirectTo<SomeOtherController>(typeof(SomeOtherController).GetMethod("SomeAction"));

View results (where the view name is the same as the action name - explicitly or via an empty string)

_controller.WithCallTo(c => c.Index()).ShouldRenderDefaultView();

// Or, if you want to check a partial is returned
_controller.WithCallTo(c => c.Index()).ShouldRenderDefaultPartialView();

View results

_controller.WithCallTo(c => c.Index()).ShouldRenderView("ViewName");

// Or, if you want to check a partial is returned
_controller.WithCallTo(c => c.Index()).ShouldRenderPartialView("ViewName");

Unfortunately, we couldn't think of a way to get rid of the magic strings here so where possible use the default ones above.

See below for view model testing.

Files

_controller.WithCallTo(c => c.Index()).ShouldRenderAnyFile();
_controller.WithCallTo(c => c.Index()).ShouldRenderAnyFile("content/type");

_controller.WithCallTo(c => c.Index()).ShouldRenderFileContents();
_controller.WithCallTo(c => c.Index()).ShouldRenderFileContents(new byte[] {1});
_controller.WithCallTo(c => c.Index()).ShouldRenderFileContents(new byte[] {1}, "content/type");
_controller.WithCallTo(c => c.Index()).ShouldRenderFileContents("text");
_controller.WithCallTo(c => c.Index()).ShouldRenderFileContents("text", "content/type");
_controller.WithCallTo(c => c.Index()).ShouldRenderFileContents("text", "content/type", Encoding.Unicode);

_controller.WithCallTo(c => c.Index()).ShouldRenderFileStream();
_controller.WithCallTo(c => c.Index()).ShouldRenderFileStream(new byte[] {1});
_controller.WithCallTo(c => c.Index()).ShouldRenderFileStream(new byte[] {1}, "content/type");
_controller.WithCallTo(c => c.Index()).ShouldRenderFileStream(new MemoryStream());
_controller.WithCallTo(c => c.Index()).ShouldRenderFileStream(new MemoryStream(), "content/type");
_controller.WithCallTo(c => c.Index()).ShouldRenderFileStream("text");
_controller.WithCallTo(c => c.Index()).ShouldRenderFileStream("text", "content/type");
_controller.WithCallTo(c => c.Index()).ShouldRenderFileStream("text", "content/type", Encoding.Unicode);

_controller.WithCallTo(c => c.Index()).ShouldRenderFilePath();
_controller.WithCallTo(c => c.Index()).ShouldRenderFilePath("file name");
_controller.WithCallTo(c => c.Index()).ShouldRenderFilePath("file name", "content/type");

It is useful to note that whilst file content is ultimately represented in memory as binary data, we enable you to test for textual content using convenience overloads.

In order achieve this - internally we reconstitute the text by assuming that the file content is UTF8 encoded. If you know our assumption to be wrong then you must explicitly tell the test method the actual file content encoding by means of the apparent encoding argument as not to cause a misleading test failure.

Http status codes

_controller.WithCallTo(c => c.Index()).ShouldGiveHttpStatus();

_controller.WithCallTo(c => c.Index()).ShouldGiveHttpStatus(404);

JSON

_controller.WithCallTo(c => c.Index()).ShouldReturnJson();

_controller.WithCallTo(c => c.Index()).ShouldReturnJson(data =>
{
    /* Assertions on the data being turned into json (data), e.g. if you are using NUnit, you might have something like: */
    Assert.That(data.SomeProperty, Is.EqualTo("SomeValue");
});

Content

_controller.WithCallTo(c => c.Index()).ShouldReturnContent();
_controller.WithCallTo(c => c.Index()).ShouldReturnContent("expected content");
_controller.WithCallTo(c => c.Index()).ShouldReturnContent("expected content", "content/type");
_controller.WithCallTo(c => c.Index()).ShouldReturnContent("expected content", "content/type", Encoding.UTF8);

TempData

_controller.Index();

_controller.ShouldHaveTempDataProperty("Message")
_controller.ShouldHaveTempDataProperty("Message", "Welcome to the website.")
_controller.ShouldHaveTempDataProperty<string>("Message", m => m.Contains("Welcome"))

It is important to note that the compiler cannot infer the generic type argument based on the given lambda expression and so you must explicitly supply a generic type argument in all cases.

It may also be important to note that the temp data test methods break the convention slightly by defining test methods directly on the Controller instead of on the immediate result of WithCallTo. This is slightly counter-intuitive at first, but ultimately, it enables you to be more dynamic when writing your tests.

Model tests

If you assert that the action returns a view of some sort there are some other methods that you can call (seen easily using intellisense). These allow you to check the model, e.g.:

// Check the type of the model
_controller.WithCallTo(c => c.Index()).ShouldRenderDefaultView()
    .WithModel<ModelType>();

// Check that a particular object was passed through as the model
_controller.WithCallTo(c => c.Index()).ShouldRenderDefaultView()
    .WithModel(expectedModel);

// Check that the model that was returned passes a predicate
_controller.WithCallTo(c => c.Index()).ShouldRenderDefaultView()
    .WithModel<ModelType>(m => m.Property1 == "hello");

// Make assertions on the model
_controller.WithCallTo(c => c.Index()).ShouldRenderDefaultView()
    .WithModel<ModelType>(m => {/* Make assertions on m */});

Note: if you use any of these model tests then it will check that the model passed through isn't null.

Model error tests

Once you have made assertions about the model you can then make assertions that particular model errors are present for properties of that model. While it's not generally the best idea to add validation logic to controllers (doing it unobtrusively is best), sometimes it's useful.

// Check that there are no model errors
_controller.WithCallTo(c => c.Index()).ShouldRenderDefaultView()
    .WithModel<ModelType>().WithNoModelErrors();

// Check that there is a model error against a given property in the model
_controller.WithCallTo(c => c.Index()).ShouldRenderDefaultView()
    .WithModel<ModelType>().AndModelErrorFor(m => m.Property1);

// Check that there isn't a model error against a given property in the model
_controller.WithCallTo(c => c.Index()).ShouldRenderDefaultView()
    .WithModel<ModelType>().AndNoModelErrorFor(m => m.Property1);

// Check that there is a model error against a specific key
// Avoid if possible given it includes a magic string
_controller.WithCallTo(c => c.Index()).ShouldRenderDefaultView()
    .WithModel<ModelType>().AndModelError("Key");

// You can chain these model error calls and thus check for multiple errors
_controller.WithCallTo(c => c.Index()).ShouldRenderDefaultView()
    .WithModel<ModelType>()
    .AndModelErrorFor(m => m.Property1)
    .AndModelErrorFor(m => m.Property2);

You can also make assertions on the content of the error message(s); these methods will look for any error messages against that particular model state key that match the given criteria:

// Equality
_controller.WithCallTo(c => c.Index()).ShouldRenderDefaultView()
    .WithModel<ModelType>()
    .AndModelErrorFor(m => m.Property1).ThatEquals("The error message.");

// Start of message
_controller.WithCallTo(c => c.Index()).ShouldRenderDefaultView()
    .WithModel<ModelType>()
    .AndModelErrorFor(m => m.Property1).BeginningWith("The error");

// End of message
_controller.WithCallTo(c => c.Index()).ShouldRenderDefaultView()
    .WithModel<ModelType>()
    .AndModelErrorFor(m => m.Property1).EndingWith("message.");

// Containing
_controller.WithCallTo(c => c.Index()).ShouldRenderDefaultView()
    .WithModel<ModelType>()
    .AndModelErrorFor(m => m.Property1).Containing("e error m");

You can chain the error property checks after any of these checks (you can only perform one of the checks though):

_controller.WithCallTo(c => c.Index()).ShouldRenderDefaultView()
    .WithModel<ModelType>()
    .AndModelErrorFor(m => m.Property1).ThatEquals("The error message.")
    .AndModelErrorFor(m => m.Property2);
comments powered by Disqus