Chapter 13: Unit Testing Web Applications
Introduction | JavaScript Unit Testing - Getting Started with Unit Testing, Creating Unit Tests, What to Test, Isolating Your Tests, jQuery UI Widget Testing | Server-Side Unit Testing - Getting Started with Unit Testing, What to Test, Isolating Your Components, Testing Your ASP.NET MVC Controllers | Summary | Further Reading |
Introduction
Unit testing, sometimes referred to as developer testing, focuses on testing small pieces of code, such as a class, that a developer is writing. These tests are critical for helping you ensure that the pieces you build work as expected and will operate correctly when combined with other parts of the application. Such testing helps support management of the application over time by ensuring that changes you make don't inadvertently affect other parts of the system.
This chapter shows you how to get started unit testing JavaScript as well as server-side code, but does not cover all aspects of unit testing. References to more detailed discussions about unit testing can be found at the end of this chapter. While the unit tests for the Mileage Stats application were written using Test-First Development (or Test-Driven Development), this chapter will only cover the test-oriented aspects of unit testing, not the design aspects.
This chapter does not cover other important aspects of testing, such as performance, stress, security, automation, deployment, localization, and globalization. Nor does it discuss other important aspects to consider when testing the client side, such as cross-browser compatibility or usability. However, these areas are important for you to consider when testing your web application.
In this chapter you will learn:
- How to get started unit testing your JavaScript and ASP.NET MVC code.
- The arrange-act-assert unit test structure.
- Techniques to isolate your tests and components.
- Things you should consider when testing your jQuery UI widgets.
The technologies discussed in this chapter are QUnit, used to test your JavaScript and jQuery client-side code, and xUnit.net and Moq, for testing your server-side, ASP.NET MVC code.
JavaScript Unit Testing
Unit testing, functional testing, and performance testing of the client-side portion of a web application present different challenges than server-side testing presents. In addition to testing the structural layout of a page and basic application functionality, you may want to verify that animations execute properly, that a page with a large amount of JavaScript has no memory leaks, and that the application maintains its functional and performance expectations across multiple browsers.
As a web application developer, you will use JavaScript for the user interface (UI) logic in your application to dynamically build the structure, to enable or disable portions of your UI, or to load data in the background. Some of this functionality may rely on libraries you adopt, such as jQuery. You'll want to be sure that each piece operates as you expect so that the application as a whole works as planned.
Unit testing allows you to verify that individual pieces work as you expect and provides a way for you to verify that they continue to work as libraries or tools evolve. For example, you may build a jQuery UI widget to manage a piece of your UI. When the next version of jQuery is released, you can quickly and easily verify that your widget is still working by executing the unit tests using the new jQuery libraries.
While unit testing isn't difficult, the learning curve can be somewhat steep for those unfamiliar with it. One common objection to adopting unit testing is the perception that it takes extra time to write unit tests. While it is true that it will take longer to build something with unit tests than without (after all, you need to write the unit tests), what is often not considered is the time you will save later in tracking down bugs or verifying that things still work after changing the code or upgrading to a new version of a library. For the uninitiated, it can also be difficult to determine what should be tested or how to approach testing for a particular behavior. Unit testing can be a complicated topic. This section seeks to provide you with the basics to get started. It will give you an idea of what to test and provide some approaches to solving common challenges in unit testing JavaScript in your application.
Getting Started with Unit Testing
The Project Silk team decided to use the QUnit unit testing framework to help with unit testing their JavaScript components, which rely heavily on jQuery and the jQuery UI widget framework, which also use QUnit. The QUnit unit testing framework can be found on the jQuery website at http://docs.jquery.com/QUnit. The site provides examples, documentation, and links to the download.
To set up QUnit, you will typically create an HTML page containing specific QUnit elements with certain class attributes specified, and add and reference the qunit.js and qunit.css files. In Mileage Stats, these were added to the tests folder under the Scripts folder.
QUnit files in project
Once this structure is in place, you will create a test JavaScript file for each set of tests you want to run. A set is typically focused on a specific JavaScript object. For example, in Mileage Stats there is a JavaScript test file for each of the jQuery UI widgets that the application implements.
Each of these JavaScript test files and the JavaScript file of the item being tested are referenced from the test.htm file so the QUnit framework can locate and execute the tests.
<!-- Contained in test.htm -->
<!-- Code under test -->
<script src="../Debug/mstats.utils.js"></script>
<script src="../Debug/mstats.events.js"></script>
<script src="../Debug/mstats.pubsub.js"></script>
...
<!-- Unit tests -->
<script src="mstats.utils.tests.js"></script>
<script src="mstats.pubsub.tests.js"></script>
<script src="mstats.data.tests.js"></script>
...
These unit tests can be run by viewing the test HTML file in a browser. From the Microsoft® Visual Studio® development system, you can right-click the test HTML file and select View in Browser. For Mileage Stats, the output would look like this while executing the tests.
QUnit test run output
Creating Unit Tests
There are typically multiple unit tests in one file and they are often grouped around a particular topic or type of test. In QUnit, the module function is used to denote a group of tests.
module('Test Group');
test('Test one', function () {
// Test logic goes here
});
test('Test two', function () {
// Test logic goes here
});
Let's look at the structure of a typical test. This is a test from mstats.data.test.js to test a data caching component within the solution.
// Contained in mstats.data.test.js
test('When data is saved, then it can be retrieved', function () {
expect(1);
// Arrange
var value = 'some-data';
// Act
mstats.dataStore.set(' /some/url', value);
// Assert
equal(
mstats.dataStore.get(' /some/url'),
value,
'mstats.datastore saved and returned ' + value);
});
Note
The test, expect, and equal methods are specific to QUnit.
Most unit tests follow an arrange-act-assert pattern, a common test pattern employed when unit testing. Its purpose is to make a clear distinction between the set up for the test, the action that is to be tested, and the evaluation of the results. In most unit tests, arrange, act, and assert occur in that order.
In the above code example, the "act" step ensures the value can be set in the store and the "assert" verifies that the value was appropriately set. The QUnit framework provides a number of functions to help with assertions. The equal assertion is shown in the example, but ok (which performs a Boolean check) is also frequently used.
Notice that we execute a single assertion in this test. In general, keep the number of assertions low (preferably to a single assertion) for smaller, more focused unit tests. Writing unit tests in this manner encourages you to write code that is also small and focused. This tends to lead to code that is more composable because each piece of code has a single responsibility. Each unit test should generally take one action and make one assertion. However, there are cases in which a group of similar assertions will be made, such as when verifying the property values on a deserialized JavaScript Object Notation (JSON) object.
The QUnit framework provides the expect function, which verifies that the proper number of expected assertions ran. At the beginning of the test you will see that expect(1) was called to let QUnit know how many assertions should be run. If QUnit does not encounter that number of assertions, then it will produce an error in its output.
What to Test
Now that you know how to write a unit test, the really important question concerns what should be tested. Generally, in a unit test you are trying to verify the functionality of a relatively small component, such as a JavaScript object or a jQuery UI widget. Each test verifies independent pieces such as whether a calculation happened correctly or whether the proper document object model (DOM) modification occurred.
When you are testing UI widgets, you may be uncertain about what should be tested. The basic rule of thumb is to test anything a designer would not change. Logic that drives the UI might be tested to determine, for example, that the proper navigation action was invoked, that an element had the proper class attribute applied (or removed), or that the correct event was raised. But, you would not test that a specific font value was set or a specific background color of an element was used.
Isolating Your Tests
Often your object under test will rely on other objects, functions, or libraries. You may have an object that makes Ajax calls to retrieve data. If you attempt to make Ajax calls when running the unit tests you might get unpredictable results because the server responding to the calls may be unavailable when you run your tests. Generally, you want to isolate your component from these types of problems.
When testing, you will also want to isolate your component from other objects you build within your system. In Mileage Stats, many jQuery UI widgets rely on a publish-subscribe object for communication. When you test objects with dependencies, you do not want to invoke the actual dependencies. If you did, you would be testing more than one thing at a time. Instead, it is important to test that the component attempts to invoke a dependency. The typical strategy for isolating your component under test is to supply an alternative component or function that the component calls instead of the real component. These alternatives may be referred to as fakes, doubles, stubs, or mocks. As it turns out, the ability to isolate your component in this manner also helps the overall design of your application because you will tend to create smaller, more focused components.
With a substitute object employed, you can verify that the correct calls were made with the right values. For example, when testing that the Mileage Stats data cache component makes an Ajax call with the appropriate URL, a mock ajax function is supplied for testing in the jQuery object (represented as the dollar sign in the example). In this alternate, we verify that the expected URL is invoked by the component.
// Contained in mstats.data.tests.js
test('when sendRequest is called, then the url from options is used', function () {
expect(1);
// Arrange
$.ajax = function (options) {
// Assert
equal(options.url, '/url', 'Url was properly set');
};
// Act
mstats.dataManager.sendRequest({
url: '/url'
});
});
Note also that this somewhat changes the typical arrange-act-assert order of the test structure because the assertion is in the supplied Ajax function. It is important to use the expect function at the beginning of your tests to help ensure that all the expected assertions are made.
When providing these alternative functions or components, it is also imperative to capture and restore the original values to avoid interfering with any other test that may have relied on these values. In QUnit this can be done when defining the test module where the setup and teardown functions can be supplied.
// Contained in mstats.data.tests.js
module(
'MileageStats DataManager sendRequest Tests',
{
setup: function () {
this.savedAjax = $.ajax;
...
},
teardown: function () {
$.ajax = this.savedAjax;
...
}
}
);
jQuery UI Widget Testing
When unit testing jQuery UI widgets, there are some additional considerations. Since a widget is attached to a DOM element, you will need to create these elements either in the test or, if they are more complicated, in the setup for a module. In Mileage Stats, because many of the widgets interact with a section of the DOM, some of that structure needs to be created during the test setup. For example, the setup for the header widget test recreates that portion of the DOM structure it manipulates.
// Contained in mstats.header.tests.js
module('Header Widget Tests', {
setup: function () {
$('#qunit-fixture').append(
'<div class="header" id="header">' +
'<div><div><h1>Dashboard</h1>' +
'<div id="notification"></div>' +
'<div class="nav">' +
'<span id="welcome">Welcome <b>Sample User</b></span>' +
'[ <a id="dashboard-link" href="/Dashboard">Dashboard</a>' +
'| <a id="charts-link" href="/Chart/List">Charts</a>' +
'| <a id="profile-link" href="/Profile/Edit">Profile</a>' +
'| <a id="login-link" href="/Auth/SignOut">Sign Out</a> ]' +
'</div>' +
'</div></div>' +
'</div>'
);
}
});
In QUnit, any DOM elements added for your test should be added to the element with the id attribute containing qunit-fixture, as shown above. You should only add the minimal amount of structure needed to appropriately simulate your test needs as this will make the structural dependencies of the test clearer.
When testing jQuery UI widgets you will also often need to supply an alternate implementation of dependent functions or objects. Because you don't control the creation of the jQuery UI widgets directly, you will typically do this as part of the options object passed into the widget (See Chapter 3, "jQuery UI Widgets" for more details about the use of an options object). For example, when testing the Mileage Stats vehicle details widget, an alternative implementation for the event publisher is supplied as part of the options.
// Contained in mstats.vehicle-details.tests.js
test('when loading data errors out, then triggers error status', function () {
expect(2);
var eventType = 'loadError',
details = $('#details-pane').vehicleDetails({
templateId: '#testTemplate',
sendRequest: function (options) { options.error({}); },
publish: function (event, status) {
if (status.type === eventType) {
ok(status, 'status object passed to publisher');
equal(status.type, eventType, 'status is of type : ' + eventType);
}
}
});
// force a data refresh
details.vehicleDetails('option', 'selectedVehicleId', 1);
});
Server-Side Unit Testing
Unit testing code on the server typically involves many more interactive pieces than what you encounter when testing client-side JavaScript. In an ASP.NET MVC application, controllers will interact with services or repositories to handle each request. As each piece is built, these interactions can be tested using unit tests to instill confidence that the system continues to work as new features are added or new versions of dependent libraries are supplied.
This section is intended to provide you with enough information to get started unit testing your server-side application. Since each application is different, illustrating all testing scenarios is not possible. To find out more about unit testing your applications, see the "Further Reading" section.
Getting Started with Unit Testing
There are a number of unit testing frameworks to choose from when unit testing server-side Microsoft .NET Framework components. Most unit testing frameworks are conceptually similar, and any one of them can be a reasonable choice. Microsoft offers two technologies that can be used for writing unit tests: Microsoft Test and xUnit.net. Microsoft Test is supplied with certain versions of Visual Studio, and xUnit.net is a Microsoft developed, open-source unit testing framework available on CodePlex and NuGet.
Regardless of your unit testing framework choice, unit tests are placed in a separate assembly that the unit testing framework can discover and use to execute the tests. A typical Visual Studio solution organization includes all the unit test files under a single solution folder with an appropriate name. For example, the Mileage Stats solution has its test projects in a Unit Tests solution folder.
Unit test location in the MileageStats project
There is a unit test project for the Services, ServicesModel, SqlCe, and Web projects. Some projects don't have a corresponding unit tests project primarily because these projects contain only shared interfaces and data transfer classes that do not have significant logic to test.
The team chose xUnit.net as the unit testing framework for the Mileage Stats project. While you can accomplish unit testing with either Microsoft Test or xUnit.net, the team chose xUnit.net because it was built specifically for developer unit testing. The remainder of this section discusses unit testing using examples in xUnit.net, but you can readily implement the same approaches with Microsoft Test or another unit testing framework, although some of the exact mechanics may be different.
To create a new unit test project, add a C# or Microsoft Visual Basic® Class Library project, and reference the xUnit.net assemblies. In the test project, there will be a class to contain all the related tests for a particular component. For example, the MileageStats.Web.Tests project contains a test class for each controller in the Web project. They generally have the same name as the controller name with the term "Fixture" appended.
To write a test, create a method with the Fact attribute specified. The xUnit.net framework searches for these attributes and executes these methods. Each test should follow the arrange-act-assert pattern. In this pattern all the setup for the test is done first (arrangement), then the action to be tested is executed (act), and then the validation is done (assert).
//Contained in ReminderFixture.cs
[Fact]
public void WhenReminderIsNotOverdue_ThenIsOverdueReturnsFalse()
{
// Arrange
var reminder = new ReminderFormModel()
{
Title = "future reminder",
DueDate = DateTime.UtcNow.AddDays(2),
DueDistance = 10000
};
reminder.UpdateLastVehicleOdometer(10);
// Act
bool isOverdue = reminder.IsOverdue;
// Assert
Assert.False(isOverdue);
}
In addition to Assert.False, xUnit.net supplies a number of other built-in asserts available on the Assert static class.
After the tests have been built, you can execute them using the xUnit.net test runner to see if they pass. The test runner is located where you unpackaged the xUnit.net contents retrieved from CodePlex. After you add the test assembly into the runner, you can run all the tests to see if they succeed.
Running unit tests
If a test fails, a message appears in the console.
Running unit tests with errors
Alternatively, you can run tests by using TestDriven.Net or Resharper, which run the tests from within Visual Studio. For more details on how to do this, see http://xunit.codeplex.com.
What to Test
On the server side, you should create unit tests for any classes and components that contain logic or must interact with other components. You should not write unit tests for generated code or code you don't own. The team wrote unit tests for classes in each of the major layers.
Repository Layer. The repository layer provides the basic persistence for information throughout the system. In Mileage Stats, this relies heavily on Entity Framework Code-First and Microsoft SQL Server® Compact Edition. Many of the tests written against this layer verify that the persistence and retrieval implementations for the various repositories produce the correct results. These tests, because they are writing to an actual database, cannot strictly be considered unit tests, but are useful in verifying that the persistence mechanism for all the models works as expected. Often, these are referred to as integration tests.
These tests were also useful because the Entity Framework Code-First library was adopted before its final release, so these tests helped demonstrate that the expectations around Entity Framework were maintained between releases.
Business Logic Layer. The business logic layer is invoked by the controllers in the Web Layer in order to execute business rules and store data in the repository. Unit tests for the business logic layer focus on verifying the business rules and their interactions with the repository layer. The tests do not actually store data in the repository, but use a fake repository and verify that the business services layer uses it correctly. The models in the business logic layer often contain validation logic that is also verified in unit tests.
Web Layer. The actual controllers that respond to requests have unit tests to verify that they interact with the services and models appropriately and return correctly built View Models for the Views or jQuery template.
Isolating Your Components
It is common for the classes you are testing to rely on other classes. For example, a class may rely on a repository to persist a model. During testing, you want to isolate the interaction of your class with these other objects to ensure that only the behavior of the class in question is tested. Additionally, it can sometimes be painful to set up these other classes appropriately. For example, if the class calls a web service, it would be unrealistic to expect that the web service be available when you want to run your unit test.
Instead of trying to create the actual context for the class under test, you supply it with alternative implementations of the object it depends on. These alternatives may also be called fakes, doubles, stubs or mocks. Using these alternative implementations has the side effect of also helping separate the responsibilities of your classes.
To provide this separation, instead of creating a class that depends on a specific technology, you can provide an abstraction for the class to depend on. This allows you to provide different implementations of the dependency at different times, such as at unit test time. Often this abstraction could be an interface definition, but it could also be a base or abstract class.
For example, suppose you had a class to test that needed to store values somewhere. Instead of tying the class directly to a specific store implementation, it can depend on an IStore abstraction.
public interface IStore
{
void Persist(string item);
}
public class ClassToTest
{
private IStore store;
public ClassToTest(IStore store)
{
this.store = store;
}
public void Save(string value)
{
...
store.Persist(value);
...
}
}
When you write a test for this class that depends on IStore, you can then provide an alternative implementation.
[Fact]
public void WhenSaving_ThenSendsValueToStore()
{
var mockStore = new StoreMock();
var classToTest = new ClassToTest(mockStore);
classToTest.Save("Test");
Assert.Equal(mockStore.ValueSaved, "Test");
}
private class StoreMock : IStore
{
public string ValueSaved { get; set; }
public void Persist(string item)
{
ValueSaved = item;
}
}
The StoreMock object captures the saved item to verify that ClassToTest sends the correct value to the store. Instead of making these mocks by hand, as shown above, the team relied on Moq—a mocking framework—when writing Mileage Stats tests. The test shown above would look like this using Moq.
[Fact]
public void WhenSaving_ThenSendsValueToStore()
{
var mockStore = new Mock<IStore>();
var classToTest = new ClassToTest(mockStore.Object);
classToTest.Save("Test");
mockStore.Verify(s => s.Persist("Test"));
}
Moq dynamically builds the objects needed for testing, and in the case of the Verify method can automatically verify that methods or properties were called with the correct values. For more information about using Moq, see the Moq CodePlex site at http://moq.codeplex.com.
There are times when you don't control the class that you want to mock. For instance, if you use a static class built into the .NET Framework library, such as FormsAuthentication, you do not have control over that class. In these cases, you will often create an interface for just the functionality you use and provide a default implementation for run-time use and a mock implementation to use at test time. This was the approach employed with Mileage Stats when using the DotNetOpenAuth library. This is a third-party library used by Mileage Stats to handle various authentication protocols. To isolate the components and make them more testable, the IOpenIdRelyingParty interface was created.
// IOpenIdRelyingParty.cs
public interface IOpenIdRelyingParty
{
ActionResult RedirectToProvider(
string providerUrl,
string returnUrl,
FetchRequest fetch);
IAuthenticationResponse GetResponse();
}
Mileage Stats has a default implementation that uses the real DotNetOpenAuth library at run time and a mock implementation when testing the AuthController MVC controller.
At run time, all these pieces are connected using a technique known as dependency injection. See "Dependency Injection" in Chapter 11, "Server-Side Implementation" to better understand how this works.
Testing Your ASP.NET MVC Controllers
ASP.NET MVC was designed to support the testability of the controllers, filters, and actions that developers typically write when developing a Model-View-Controller (MVC) application. Since each controller is responsible for handling a request, and MVC automatically maps input from the query string or from form data to the data types on your controller's methods, you can easily write tests for your controllers and simply supply them with the necessary inputs. For instance, the ReminderController's Add method takes an integer value for the vehicle identifier, and a Reminder object.
// Contained in ReminderController.cs
public ActionResult Add(int vehicleId, ReminderFormModel reminder)
{
...
return View(viewModel);
}
In a unit test, it is very simple to provide these parameter values during testing of the Add method. The example below demonstrates how you would supply the reminder and vehicle ID directly in the test.
// Contained in ReminderControllerFixture.cs
[Fact]
public void WhenAddReminderWithValidReminder_ThenReturnsToReminderDetailsView()
{
...
var result = (RedirectToRouteResult)controller.Add(vehicle.VehicleId, formModel)
;
Assert.NotNull(result);
Assert.Equal("Details", result.RouteValues["action"]);
Assert.Equal("Reminder", result.RouteValues["controller"]);
}
While many unit tests for controllers can use this approach, there are still cases in which the controllers require access to the HttpContext. Providing alternate implementations of HttpContext is usually very difficult, making certain scenarios very hard to test. But because the MVC base controller class, Controller, relies on the abstract HttpContextBase class instead of HttpContext itself, it can be substituted much more easily. Mileage Stats uses a mock HttpContextBase in many of its controller tests to ensure that the HttpContextBase.User property is set appropriately.
To do this, Mileage Stats uses an MvcMockHelpers class that wraps the building of a Moq object that substitutes HttpContext information. This controller context is then set on the controller under test by calling the static SetFakeControllerContext extension method in the MvcMockHelpers class. The RemindersControllerFixture sets this when it builds a testable controller.
// Contained in ReminderControllerFixture.cs
private ReminderController GetTestableReminderController()
{
var controller = new ReminderController(_mockUserServices.Object, _serviceLocator.Object);
controller.SetFakeControllerContext();
controller.SetUserIdentity(new MileageStatsIdentity(_defaultUser.AuthorizationId,
_defaultUser.DisplayName,
_defaultUser.UserId));
return controller;
}
The fake context creates a series of Moq objects that the controller will interact with under test. If you want to adjust what they're doing, you can recover the mock and change its behavior. The static SetUserIdentity extension method above does this for a controller to set an identity context for the test into the HttpContext.
// Contained in ControllerMockHelpers.cs
public static void SetUserIdentity(this Controller controller, IIdentity identity)
{
Mock.Get(controller.HttpContext)
.Setup(x => x.User)
.Returns(new GenericPrincipal(identity, null));
}
The types of tests you typically write around your controller include:
- View Models. You will want to test that the controller provides the correct model data for a specific view.
- Navigation. You will want to test that the controller will provide the correct redirection when it is finished processing the request or when there is an error processing the request.
- Interaction. You will want to test that the controller makes the appropriate calls to your repository or service layers (which will be mocked in the tests). You will also want to test that your controller appropriately handles the situation when the model data supplied to a controller is in an invalid state.
- JSON Endpoints. If you have JSON data endpoints, you want to make sure these return appropriate JSON results for the call.
Summary
You should make a conscious decision about whether or not you are going to unit test your code. Unit testing is not difficult, but it does require an investment of time to learn and apply. However, the time initially spent writing unit tests will save time over the life of your project and deliver better quality code. Frameworks that help you write unit tests are available for most languages and platforms. Visual Studio includes unit test support for C# and Visual Basic, among other languages, and you can readily find them for languages such as JavaScript.
Further Reading
Meszaros, Gerard. xUnit Test Patterns: Refactoring Test Code. Addison-Wesley, 2007.
Kaner, Cem, Jack Falk, and Hung Q. Nguyen. Testing Computer Software, 2nd Edition. Wiley, 1999.
patterns & practices Acceptance Test Engineering Guidance on CodePlex:
http://testingguidance.codeplex.com/
Performance Testing Guidance for Web Applications on MSDN®:
https://msdn.microsoft.com/en-us/library/bb924375.aspx
Guidance for Build, Deploy and Test Workflows on MSDN:
https://msdn.microsoft.com/en-us/library/ff972305.aspx
QUnit unit testing framework on the jQuery website:
http://docs.jquery.com/QUnit
xUnit.net on CodePlex:
http://xunit.codeplex.com.
Moq on CodePlex:
http://moq.codeplex.com
"Testing the User Interface with Automated UI Tests" on MSDN:
https://msdn.microsoft.com/en-us/library/dd286726.aspx
"How to: Create a Coded UI Test" on MSDN:
https://msdn.microsoft.com/en-us/library/dd286681.aspx
Resharper:
http://www.jetbrains.com/resharper/
TestDriven.NET:
http://testdriven.net
For more information on dependency injection in Mileage Stats, see Chapter 11, "Server-Side Implementation."
For more information about testing in Project Silk, see the following: