The latest Razor View Model of ASP.Net MVC 3/MVC 4 uses considerable amount of JavaScript/JQuery. Sometimes the ActionResult returned by the Controller methods are refined further using JQuery methods before the result in the view. It’s very common when we are returning JSON formatted output from the controller method.
We have various options to run the unit test on the .Net code written within controller. But sometimes it’s required to test those JavaScript/JQuery codes as well, to check the sanity of the output especially in the situation mentioned above.
QUnit (http://nuget.org/packages/QUnit-MVC, a NuGet package) can help us to run such Unit Test. As per its description:
“QUnit is a powerful, easy-to-use, JavaScript test suite. It's used by the jQuery project to test its code and plugins but is capable of testing any generic JavaScript code (and even capable of testing JavaScript code on the server-side).”
Let’s see a step by step example of how to use QUnit successfully with ASP.Net MVC project:
Step 1: Let’s create an ASP.Net MVC 4 Web Application.
Image may be NSFW.
Clik here to view.
Step 2: Choose Internet Application Template. No need to create Unit Test Project.
Image may be NSFW.
Clik here to view.
Step 3: Once the application created, add following 2 models (Country.cs and City.cs) under “Models” folder in solution explorer.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
namespace QUnitMvcApp.Models
{
public class Country
{
public string Code { get; set; }
public string Name { get; set; }
public static IQueryable<Country> GetCountries()
{
return new List<Country>
{
new Country
{
Code = "AU",
Name = "Australia"
},
new Country
{
Code = "UK",
Name = "United Kingdom"
}
}.AsQueryable();
}
}
}
==========================================================================
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
namespace QUnitMvcApp.Models
{
public class City
{
public string Code { get; set; }
public int CityID { get; set; }
public string CityName { get; set; }
public static IQueryable<City> GetCitys()
{
return new List<City>
{
new City
{
Code = "AU",
CityID=1,
CityName = "Perth"
},
new City
{
Code = "AU",
CityID=2,
CityName = "Sydney"
},
new City
{
Code = "AU",
CityID=3,
CityName = "Melbourne"
},
new City
{
Code = "UK",
CityID=4,
CityName = "London"
},
new City
{
Code = "UK",
CityID=5,
CityName = "Bermingham"
},
new City
{
Code = "UK",
CityID=6,
CityName = "Manchester"
}
}.AsQueryable();
}
}
}
=======================================================================
Step 4: Add following few methods to the HomeController.cs file:
public ActionResult CascadingList()
{
return View();
}
public SelectList GetCountrySelectList()
{
var countries = Country.GetCountries();
return new SelectList(countries.ToArray(),
"Code",
"Name");
}
public ActionResult CountryList()
{
var countries = Country.GetCountries();
if (HttpContext.Request.IsAjaxRequest())
return Json(GetCountrySelectList(), JsonRequestBehavior.AllowGet);
return RedirectToAction("CascadingList");
}
public ActionResult CityList(string ID)
{
string Code = ID;
var cities = from s in City.GetCitys()
where s.Code == Code
select s;
if (HttpContext.Request.IsAjaxRequest())
return Json(new SelectList(
cities.ToArray(),
"CityID",
"CityName")
, JsonRequestBehavior.AllowGet);
return RedirectToAction("CascadingList");
}
Also add following using statement HomeController.cs:
using QUnitMvcApp.Models;
Step 5: Add a new view “CascadingList.cshtml” under View>Homes folder with following code:
Image may be NSFW.
Clik here to view.
@{
ViewBag.Title = "CascadingList";
}
@{Html.EnableUnobtrusiveJavaScript(true);}
<script type="text/javascript">
$(document).ready(function () {
getCountry();
});
function getCountry() {
var url = '@Url.Content("~/Home/CountryList")/';
$.getJSON(url, function (data) {
var items = "<option>Select a Country</option>";
$.each(data, function (i, country) {
if (country.Value.indexOf("\'") > -1) {
s = country.Value + " " + country.Text;
alert(s + ": Country.Value cannot contain \'")
}
items += "<option value='" + country.Value + "'>" + country.Text + "</option>";
});
$('#CountriesID').html(items);
});
}
function getCity() {
var url = '@Url.Content("~/Home/CityList")/' + $('#CountriesID').val();
$.getJSON(url, function (data) {
var items = '<option>Select a City</option>';
$.each(data, function (i, city) {
items += "<option value='" + city.Value + "'>" + city.Text + "</option>";
});
$('#CitiesID').html(items);
});
}
</script>
<h2>Country</h2>
<div id="CountriesDivID">
<label for="Countries">Countries</label>
<select id="CountriesID" name="Countries" onchange="getCity()"></select>
</div>
<div id="CitiesDivID" >
<label for="Cities">Cities</label>
<select id="CitiesID" name="Cities"></select>
</div>
Step 6: Open up “_Layout.cshtml” page under Views>Shared folder and add following line under the tab menu section:
<li>@Html.ActionLink("CascadingList", "CascadingList", "Home")</li>
Also add following line under the head tag of the same file:
<script src="@Url.Content("~/Scripts/jquery-1.7.1.min.js")" type="text/javascript"></script>
Image may be NSFW.
Clik here to view.
Now press F5 to debug this application and go to CascadingList tab. You can see our JQuery/JavaScript code is working fine and filling up the drop-down list boxes as expected after receiving JSON formatted output from the controller.
Image may be NSFW.
Clik here to view.
Ok, it’s time now to set up some Unit Test on the JavaScript code we used in the CascadingList view.
Step 7: Add a reference of the NuGet package QUnit.
Image may be NSFW.
Clik here to view.
Search and install QUnit:
Image may be NSFW.
Clik here to view.
Step 8: Add following 2 lines to _Layout.cshtml
<script src="@Url.Content("~/Scripts/qunit.js")" type="text/javascript"></script>
<link href="@Url.Content("~/Content/qunit.css")" rel="stylesheet" type="text/css" />
Image may be NSFW.
Clik here to view.
Step 9: Now it’s time to convert our code into more “Unit Testable” Format. Instead of calling the getCity() function from the onchange event of the select “Country”, we will add the code into the OnLoad event handler. Let’s look into the changed CascadingList.cshtml file:
@{
ViewBag.Title = "Cascading List";
}
@{Html.EnableUnobtrusiveJavaScript(true);}
<script type="text/javascript">
var cityCount = 0;
$(document).ready(function () {
getCountry();
/////////////Code Added/////////////////////
$('#CountriesID').change(function () {
getCity($('#CountriesID').val());
});
/////////////Code Added/////////////////////
});
function getCountry() {
var url = '@Url.Content("~/Home/CountryList")/';
$.getJSON(url, function (data) {
var items = "<option>Select a Country</option>";
$.each(data, function (i, country) {
if (country.Value.indexOf("\'") > -1) {
s = country.Value + " " + country.Text;
alert(s + ": Country.Value cannot contain \'")
}
items += "<option value='" + country.Value + "'>" + country.Text + "</option>";
});
$('#CountriesID').html(items);
})
}
/////////////Code Removed/////////////////////
@*function getCity() {
var url = '@Url.Content("~/Home/CityList")/' + $('#CountriesID').val();
$.getJSON(url, function (data) {
var items = '<option>Select a City</option>';
$.each(data, function (i, city) {
items += "<option value='" + city.Value + "'>" + city.Text + "</option>";
});
$('#CitiesID').html(items);
});
}*@
/////////////Code Removed/////////////////////
/////////////Code Added/////////////////////
function getCity(countryId) {
var url = '@Url.Content("~/Home/CityList")/' + countryId;
$.getJSON(url, function (data) {
var items = '<option>Select a City</option>';
$.each(data, function (i, city) {
items += "<option value='" + city.Value + "'>" + city.Text + "</option>";
cityCount++;
});
$('#CitiesID').html(items);
});
}
/////////////Code Added/////////////////////
</script>
<h2>Country</h2>
<div id="CountriesDivID">
<label for="Countries">Countries</label>
@*<select id="CountriesID" name="Countries" onchange="getCity()"></select>*@
<select id="CountriesID" name="Countries"></select>
</div>
<div id="CitiesDivID" >
<label for="Cities">Cities</label>
<select id="CitiesID" name="Cities"></select>
</div>
Now press F5 and you will see everything is working as it was before.
Step 10: Add code for Unit Testing. This is how the Script block will look like after the change:
<script type="text/javascript">
var cityCount = 0;
$(document).ready(function () {
getCountry();
/////////////Code Added/////////////////////
$('#CountriesID').change(function () {
//getCity($('#CountriesID').val());
getCity('AU');
});
/////////////Code Added/////////////////////
});
function getCountry() {
var url = '@Url.Content("~/Home/CountryList")/';
$.getJSON(url, function (data) {
var items = "<option>Select a Country</option>";
$.each(data, function (i, country) {
if (country.Value.indexOf("\'") > -1) {
s = country.Value + " " + country.Text;
alert(s + ": Country.Value cannot contain \'")
}
items += "<option value='" + country.Value + "'>" + country.Text + "</option>";
});
$('#CountriesID').html(items);
})
}
/////////////Code Removed/////////////////////
@*function getCity() {
var url = '@Url.Content("~/Home/CityList")/' + $('#CountriesID').val();
$.getJSON(url, function (data) {
var items = '<option>Select a City</option>';
$.each(data, function (i, city) {
items += "<option value='" + city.Value + "'>" + city.Text + "</option>";
});
$('#CitiesID').html(items);
});
}*@
/////////////Code Removed/////////////////////
/////////////Code Added/////////////////////
function getCity(countryId) {
var url = '@Url.Content("~/Home/CityList")/' + countryId;
$.getJSON(url, function (data) {
var items = '<option>Select a City</option>';
$.each(data, function (i, city) {
items += "<option value='" + city.Value + "'>" + city.Text + "</option>";
cityCount++;
});
$('#CitiesID').html(items);
});
}
/////////////Code Added/////////////////////
////////////////////Test Code/////////////////////////
test("Perform Country onchange", function () {
$('#CountriesID').trigger("change");
alert("Check No. of cities from Australia");
equals(cityCount, 3, "The no. of cities should be 3");
});
//////////////////////////////////////////////////////
</script>
And after the last </div> tag we add:
<div>
<h1 id="qunit-header">QUnit example</h1>
<ol id="qunit-tests"></ol>
</div>
Now press F5 once again, browse to CascadingList and you will see we are actually triggering the onchange event of the Country select box with predefined value to run Unit Test successfully. We have added an alert message just to make sure the effect of onchange gets sufficient time to load.
Image may be NSFW.
Clik here to view.
So, we have successfully implemented Unit Test on JavaScript written within our ASP.Net MVC application. Happy QUnit-Testing.
Image may be NSFW.
Clik here to view.