Introduction
In this article, I want to give you some handles to structure your MVC applications in such a way that they become easier to test. The article isn’t really about MVC, but if you want more information on MVC, I put some references at the end of this article.
MVC is a pattern typically used for creating web applications. Of course, it can be (and is) applied for other types of applications as well. In this article, I only talk about ASP.NET MVC.
One of the reasons to separate the application in (at least) model – view – controller is to promote testability. We want to be able to test the application with as few dependencies as possible.
In this post, I only concentrate on testing the server side, which will be more than enough for one post.
MVC Responsibilities
I only give a short introduction, to make sure that we’re all on the same page.
<Yoda voice> More to say there is! </Yoda voice>
The View
To start with the easiest one: the view will be sent to the user, typically as HTML. The view can contain all the logic if you wish, because in the Razor syntax, you can use everything in C# that you can use elsewhere. BUT that is not the idea. The view should bind variables, walk over collections, and generate the HTML from ready made data. In the view, there should be as little processing as possible (server side).
Your view can also contain logic in JavaScript for code that is to be executed in the client browser, ultimately resulting in a Single Page Application.
The Controller
The client request is routed from the browser to the controller. Of course, this is a simplification, but it will do for now. The controller contains methods with zero or more arguments that the routing system will call. This is done automatically in the framework.
The responsibility of the controller is to use the arguments to generate the output needed in the view. For example, arguments can be used to obtain customers for a certain ZIP code. In this case, the controller will obtain only the required customers and send them into the view. As we saw before, the view will receive this data from the controller and represent it.
Slightly more advanced: the controller can also choose to display a different view. The principle remains the same though: the view receives the data and renders is.
We want to keep the controller as simple as possible, so we let us help by the Model
classes. You may notice that I’m already trying to split up complex parts into simple parts to make them easy to test – as is the purpose of this article.
The Model
The Model contains most of the classes that will be used in the Controllers and in the Views. Try to keep these classes as simple as possible as well and separate responsibilities. The Model is often split into 2 specific parts.
Data Model
Typically, these are classes generated by Entity Framework (if you use database first), or your code first classes. I also like to include the repositories in the data model.
Other classes that can go in here are classes that are generated from SOAP or REST web services (by adding a web service proxy to your project).
These classes are mainly used in the Controllers to either modify data, or to obtain data (or both).
Viewmodel
As the name implies the ViewModel is used by the views. In a small application, it may be overkill to create a separate ViewModel , and you can use the classes from the data model. But very soon, the ViewModel will contain more (or other) information than the Data model:
- The ViewModel may contain only those fields that are necessary in the view(s).
- It may contain other field names, in case this is clearer for the View. Sometimes, field names in a database have to follow some (company) rules, or names from a web service may be very generic names. In those cases, translating them into something more “speaking” may help your designers. The developer who creates the Controllers and other back-end code and the front-end developer are often not the same guy (or girl).
- It may contain calculated fields, aggregated fields, transformed fields, …
- It may contain extra annotations to indicate to the user interface that fields are mandatory, have specific validations, have default values, different (localized) captions. These can then be picked up in the View to automatically generate some validation rules.
- etc.
This means that the responsibility of the Controller now becomes:
- Use the arguments to obtain data. The data will be in the Data Model format
- Convert the obtained data into ViewModel classes
- Pass this ViewModel to the View, which can then represent the data
Converting from the Data Model to the View Model
If the conversion is straightforward, then it may be handy to use a library like AutoMapper, which will take care of the mapping of the fields for you. If the mappings become more complex, I would advice to write specific conversion classes and methods. AutoMapper is capable of a lot of customization in its mapping, but you risk to complicate your code more than by writing a simple conversion function. Think also about the poor guy who needs to debug this code. Usually, that’s you!
It will be clear now that the conversions must be tested as well. The tests can be simple / obvious, but when you extend or modify your classes, the tests will fail if you don’t adapt your conversions as well. This will create a nice TODO list for you…
Setting Up the Tests
Now that we have briefly determined the responsibilities of the MVC parts, we can set up and implement tests.
Setting up the Test Project
If you haven’t already created a test project, do so now (refer to my previous posts about this if you are not sure how to do this). A shortcut can be to right-click the Index
method and then select “Create Unit test”. This will present you a nice dialog and do all the hard work for you.
Because we are going to test an MVC application, based on the ASP.NET MVC classes, we’ll also need to install the Microsoft.AspNet.Mvc
Nuget package. You can do this in the Package Manager Console (Tools > Package Manager > Package Manager Console) and type:
install-package Microsoft.AspNet.Mvc
Also add a reference to the “Microsoft.CSharp
” assembly. This will make sure that you can use the “dynamic” type in your tests.
Testing the Model
This should be easy because these are just normal classes. Feel free to read my other articles on this in the Testing category of this site.
Typically, the Model classes will access a database, or call web services. For the unit tests, this must be mocked of course, for example, using a library such as MOQ. The other MVC classes will depend on the Model. So make sure you put enough effort in testing the Model classes.
Testing the Controller
As we saw before, the controller must orchestrate some calls into the Model, and bring together the results. These results are then passed into the view to be represented. So in most cases, you don’t want to generate representation data in the controller, as that is the view’s responsibility.
Let’s take the example of a simple (stubbed) AgendaController
:
public class AgendaController : Controller
{
// GET: Admin/Agenda
public ActionResult Index()
{
List<Agenda> agendas = new List<Agenda>();
agendas.Add(new Agenda { Description = "Dr.X", Id = 1 });
agendas.Add(new Agenda { Description = "Dr.No", Id = 2 });
agendas.Add(new Agenda { Description = "Dr.Who", Id = 3 });
List<String> resources = new List<String>();
resources.Add("Agendas");
ViewBag.Resources = resources;
return View(agendas);
}
}
In the Index()
function, a list of Agendas is created, and in this case filled in with some random data. The same is done with a list of resources and then 2 methods of data passing are used:
ViewBag
: This will create a new property on the dynamic object ViewBag
, to be able to pass the resources collection in the View. return View(Agendas)
: This will use the Model
property in the view, which will contain this collection. The data type of Model is determined in this line:
@model IEnumerable<Planning365.Data.Agenda>
This prevents us from having to cast Model
everywhere in the View.
Writing the Test for AgendaController.Index( )
I choose an easy example to test, with no parameters; but the principles remain the same.
[TestClass()]
public class AgendaControllerTests
{
[TestMethod()]
public void IndexTest()
{
// arrange
AgendaController sut = new AgendaController();
// act
ViewResult res = (ViewResult)sut.Index();
List<String> resources = res.ViewBag.Resources;
List<Agenda> agendas = (List<Agenda>) res.Model;
// assert
Assert.IsTrue(agendas.Any(a => a.Id == 1));
Assert.IsTrue(agendas.Any(a => a.Id == 2));
Assert.IsTrue(agendas.Any(a => a.Id == 3));
Assert.IsTrue(resources.Contains("Agendas"));
Assert.AreEqual("", res.ViewName);
}
}
Using the AAA pattern, we first arrange the test. In this case, it is only 1 line, instantiating the controller. The controller is just a normal CLR class, which happens to derive from the MVC Controller base class, so its instantiation is simple. If you use dependency injection, then the steps in the “arrange” phase may be:
- Create a mocked instance of the classes to be injected
- Use the right constructor to pass this into the class
In the “act” phase, we call the Index
method. I also create some local variables to store the results that are to be tested. As we said when describing the controller, we use 2 ways to pass data into the view, and that is the reason that we have these 2 lines here. The “resources” variable retrieves the controller data via the ViewBag
, the “agendas” variable retrieves its data via the Model
. Notice that the Model
needs to be cast to use it.
The assertions use the obtained controller data to make sure that it is correct. These are normal Assert statements, nothing fancy going on.
In the last assertion, I test that the ViewName == “”
. This is the case when you don’t specify a ViewName
in the Controller. If you return different Views from the Controller depending on some arguments, then this is the way to test if the correct View is returned.
Testing the View (?)
There is no straightforward way to test your Views in the MVC Framework, which is a good indication that this is not a good idea. There are some libraries and Open Source projects to test Views, but is it worth it?
If all is well, your View doesn’t contain any business logic. This should be in the Model
classes, and possibly also in the Controllers of your project. So the view only contains representation logic. It will bind values to HTML controls, possibly loop over a collection and that’s it.
Also Views may change a lot. This is what a user sees from the application, so users will probably want to change lay-out, order of fields, colors, … You don’t want to adapt your tests each time a lay-out change has occurred.
So in my opinion, it usually is a smell if you need to test your Views. And it is better to deal with the smell than to invest time in testing your Views.
Conclusion
The MVC framework has been created to be testable from the beginning. This is clearly demonstrated by the nice Separation of Concerns (MVC) in the framework. But as always, it is still possible to mess things up.??
The advice I try to give in this article is to make small testable Model
classes that are easy to test. Then put them together in the Controller, with the necessary conversions. The conversions are preferably in separate (testable) methods.
The Controller methods can then be tested in the usual way. There are some small caveats in the setup of the test project.
Did I forget something? Would you do things differently? Say it in the comments section!
References