Creating Tests
What Getting Started series about ASP.NET MVC would be complete without a primer on writing tests? Here you will see a couple simple tests written for a controller action.
The sample we will use for writing the tests is the same one for the tutorial on creating a basic form, the Tigers Assault the Monkeys sample.
When you created the project you should have created a unit tests project. Look inside that and you will see something like the following image. I deleted some files and added one, so your view will look slightly different.
Screenshot of the tests project.
Since yours will not have the ZooController class, and if you are following along, right-click on the controllers folder and hit Add > New Test. You will see the following dialog:
Screenshot of the add test dialog.
Change the name to ZooControllerTest, click "OK" and you have a test class. The tests I want to write do not require all of the generated code, so I pruned quite a bit. It looked like this after the pruning:
using System;
using System.Web.Mvc;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using HowMVCWorks.Controllers;
namespace HowMVCWorks.Tests.Controllers
{
///
/// These are the tests for the ZooController.
///
[TestClass]
public class ZooControllerTest
{
public ZooControllerTest() { }
}
}
As you can see I also added a namespace reference for my controllers in the other project. Now, to write some tests. The first (TigersAssaultingMonkeysHas75PercentKillRate) will be a test to confirm that the number of monkeys killed by a tiger raid is at 75%. The second (TigerAssaultOnlyKillsUpTo100Monkeys) will make sure that the maximum number of monkeys killed is 100. Here are the tests:
using System;
using System.Web.Mvc;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using HowMVCWorks.Controllers;
namespace HowMVCWorks.Tests.Controllers
{
///
/// These are the tests for the ZooController.
///
[TestClass]
public class ZooControllerTest
{
public ZooControllerTest() { }
[TestMethod]
public void TigersAssaultingMonkeysHas75PercentKillRate()
{
ZooController controller = new ZooController();
ViewResult result = controller.TigersAssaultTheMonkeys(50) as ViewResult;
Assert.IsNotNull(result);
int numberOfMonkeysEaten = Convert.ToInt32(result.ViewData["NumberOfMonkeysEaten"]);
Assert.IsTrue(numberOfMonkeysEaten == 38);
}
[TestMethod]
public void TigerAssaultOnlyKillsUpTo100Monkeys()
{
ZooController controller = new ZooController();
ViewResult result = controller.TigersAssaultTheMonkeys(1000) as ViewResult;
Assert.IsNotNull(result);
int numberOfMonkeysEaten = Convert.ToInt32(result.ViewData["NumberOfMonkeysEaten"]);
Assert.IsTrue(numberOfMonkeysEaten == 100);
}
}
}
Both tests are very similar, but test the controller actions with different inputs. Those tests exercise the following code:
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult TigersAssaultTheMonkeys(int? numberOfTigersSent)
{
int numberOfMonkeysEaten = 0;
if (numberOfTigersSent.HasValue)
{
double numberOfPotentialMonkeysEaten = numberOfTigersSent.Value * .75;
if (numberOfPotentialMonkeysEaten > 100)
{
numberOfMonkeysEaten = 100;
}
else if (numberOfPotentialMonkeysEaten > 0)
{
numberOfMonkeysEaten = Convert.ToInt32(Math.Round(numberOfPotentialMonkeysEaten, 0));
}
}
else
{
ViewData.ModelState.AddModelError("NumberOfTigersSent", "Yo, whassup with that tiger count value?");
}
ViewData["NumberOfMonkeysEaten"] = numberOfMonkeysEaten;
return View();
}
Controllers Are Testable!
This is a big point: controllers are testable with relative ease. In Webforms the rough equivalent of the controller is the code-behind, and those were not easy to test because of the page lifecycle and various Http dependencies. By severing the necessary link with those dependencies, controllers are testable with ease as long as those dependencies are not introduced by the controller in its implementation.
Even though every controller class inherits from Controller, the class can be instantiated like most classes and the action methods can be called for the testing.
The Awesome - ActionResult
Controllers are easily testable because they do not have necessary dependencies, as mentioned above. Controller actions are testable because the results of actions are expressions of intent and not the actual execution of Http related commands.
Say you return a view from the action method of a controller. The "return View();" like you see above is not actually returning Html as the result of the action method. It is returning a ViewResult, which has properties and values that can be tested for correctness. The runtime is what takes that ViewResult and makes Html out of it.
As another example, the same can be said about the RedirectResult. It does not redirect the user; after all, if it did that would add quite a bit of complexity to automating the test. What it does is express to whatever calls it that a redirect is requested. If the ASP.NET MVC runtime is the one calling the method, a command to redirect will be issued. But if a testing library calls the method, the test code can interrogate the object to make sure the proper redirect command was returned.
Test How You Wish
Some people write automated tests for their code after they are finished writing it. So they write the controller action then write its corresponding tests. Some write their tests first then the implementation code, and some of them call this Test Driven Development (aka TDD). Either way is clearly workable and writing good automated tests if valuable whether you write them first or last. Though on the surface the TDD approach seems to make little sense, in my experience it has some positive benefits. Regardless, one of MVC's goals was to make it easier to test web applications and they succeeded. Do yourself a favor and take advantage of that.
Speak Your Mind!
Have something to say? Find a grammatical mistake? Think I said something incorrect? Don't like my perspective? Hate my color scheme? Whatever it is, you can let me know. I would appreciate it if you did.
