Testing is hard to get right. There are several ways to shoot yourself in the foot. Testing an SPA (single page application) adds its own idiosyncrasies and complexities. Utilizing well known patterns and practices and avoiding common pitfalls, it is possible to create a fast, maintainable test-driven development process.
This post explains theory behind how to write good unit tests and delves into writing a real world test case with code and full working examples. It uses Durandaljs, but the concepts are applicable to all single page applications.
What makes a good unit test?
A common mistake is to write tests at the wrong level, which ends up coupling your test to the implementation details of your code. A good indicator you’ve got it wrong is tests breaking when refactoring. The way to avoid this problem is to always write tests from the public API of your application, with a “unit” being a single use case of the system under test. For an SPA , the public API of the application is the user interface itself, so in our tests we would expect to be clicking buttons, typing into input boxes, etc.
Another property of a good unit test is that it has full control of the running parts and is isolated from external dependencies, including DB, network or file access, system time, random number generation, etc. It is important that the architecture of your SPA has a way to replace these parts of the application with dummy (mock or stub) implementations. For instance, SPA unit tests will need a way to mock AJAX calls to return fake data in order to remove the dependency on the server. This is essential, as we need unit tests to be repeatable, consistent and fast.
Finally, an often over-looked aspect of tests is readability and maintainability. The intent of a test should be immediately understandable to even non-technical people. The test suite should read like manual for what the application does and how to use it. If tests are hard to decipher, developers are more likely to write new tests rather than amend existing ones when features change, leading to duplication and fragmentation of structure. On the flip-side, good tests will serve as a starting point to discussions when working within a particular functional area. A well known pattern for testing UI code is the page model, which involves creating an abstraction in your tests that represents the user interaction with your application. The concept is good, but I dislike the name, as it suggests creating an abstraction per page, whereas use cases often involve a single component within a page. We’ll see later how this can help make tests more readable.
The development process and BDD
Traditionally, and still the case for many teams, unit tests are the domain of developers. They are the things that managers know the developers are doing, but not quite sure why and have no visibility on, but accept because they are increasing the Quality™ of the product. I see this as very much an anti-pattern. Everyone should care about the tests as they define how the application should behave. If you are writing tests that only developers understand, that is another indicator you are testing at the wrong level.
A good way to ensure everyone cares is to get everyone involved in defining the tests. At Huddle, before the start of a sprint, we collaborate on the acceptance criteria of our stories, which are normal just bullet points written by the product manager that we re-write and expand on using the Gherkin syntax. Each test case is a scenario that describes a single function of the application. The formal structure of Gherkin forces out ambiguities and unknowns in the original story and allows us easily discuss points of contention. Also, when developing we can be free to concentrate on the implementation rather than worrying about the business rules. For tips on writing good scenarios, I recommend reading Specification by Example.
Testable SPA architecture
Composition is also key. We need to be able to build the full application from components that are individually testable. We should be able to take an individual use case and exercise that functionality without having to invoke unrelated components.
For these reasons (and many others) we decided to go with Durandal to handle most of the infrastructure concerns of our SPA. It is built around the core concepts of modules and composition and makes it simple to isolate components.
Enough waffle, let’s see some code…
Durandal comes with a starter kit example application that I’m going to use to show how we follow a TDD approach to SPA development.
First, lets define a scenario for the Flickr example page:
Now lets take that scenario and create a test using ScenarioJS:
Now, we need a way to run the tests. The usual way to start a Durandal application is like this:
For our test setup, we need to have a different entry point so the test can “drive” the application. So copy the usual index.html page into a test.html page that points to main-test.js instead. Inside main-test.js we use the testRunner module from ScenarioJS:
The test runner picks the test to run using window.location.hash to indicate the module id of the scenario. It loads the module and runs each step of the scenario in order. Here is this test running in the browser, open the browser console to see the test output.
Mocking AJAX calls
In the given step, we are setting up fake data for the ajax call to flickr:
The FlickrItemBuilder is an implementation of the builder pattern responsible for building that data, we use the builder pattern because api data can quickly get complicated to create. The builder pattern sets sensible defaults for the data, so you only need to specify in the test things that are relevant, which makes it more readable. The times() method creates an array of flicker items by calling the build method x times:
The mockHttp module is a module with the same public api as the durandal http plugin. It also has a fluent interface for setting up responses for specific requests. In the above we are mocking jsonp requests with the uri, data and callbackparam specified. We replace the http module in our test setup by explicitly setting the module in the require paths:
Modelling the user interaction
In the when step, we call setRoot on our application model, passing the module we are testing and the test model of that module:
The application model setRoot method calls into Durandal’s set root and waits for the module’s view to be available in the DOM. Durandal’s composition framework means that we can pass any module we want to the setRoot method and it will compose the module and its view. This is a really nice way to achieve isolation of the component under test. You don’t have to navigate from the root of your application each time to run a particular scenario, you can go straight to the module:
Our application model SetRoot returns a promise, which ScenarioJS understands, so the next step will not run until the view is rendered. We also pass the result of the promise to the next step, which in this case is the flickr test model:
The application model creates the flickr model when the module has finished loading. It passes the jquery object for the view to the constructor of the flickr model. We can then encapsulate information about the component and user interactions such as button clicks. To actually select dom elements, I added a data-test attribute to the appropriate elements so we can use a convention based approach to setting up the test model. We are hiding the details of interacting with the application from the test, so are the tests are more readable. We don’t need to know what jquery selectors are used in order to understand the intent of the test.
Now we can make assertions on the model:
ScenarioJS includes a console runner that will generate an html page with the status of the last full test run, which can be viewed here.
All the code for the durandal tests is on my github branch.