Unit Testing with Jasmine: An Introduction
In this post I’ll discuss getting started with unit testing with Jasmine, including how to set up tests for an Angular 2 application. I’ll be using a sample app — an Express app that uses Angular 2 for the client-side code and interacts with the Twitter API on the back end. To read an explanation of how I built this app, see my earlier post here. You can also see the entire project on my Github here.
Jasmine/Angular 2 Testing Setup
- npm install “jasmine-core” and save as a dev dependency
npm install --save-dev jasmine-core
2. Create a “unit-tests.html” file in the root of the client directory and include the script tags for the Jasmine library and SystemJS library and the systemjs.config.js file.
3. Import each spec file individually in unit-tests.html:
4. Either start the Express server from the root directory by running “node ./bin/www” (or using nodemon), or spin up an http server inside the client directory using npm’s serve package or Python SimpleHTTPServer (because we’re only testing the client-side code, we don’t actually need the whole server to be running).
Then navigate to the unit-tests.html page in the browser:
Note: For running Jasmine specs from the command line, you can instead use the npm jasmine command line package here. Jasmine also provides useful steps for getting started with the command line version, found here.
Writing the Unit Test Specs
When I first learned about unit testing, I was introduced to the concept of “Given, When, Then”. The idea, explained concisely in an article by Martin Fowler here, is that you first perform the setup for your test (the “given”), next the functionality you want to test (the “when”), and then check the actual result against the expected result (the “then”). My mentor has also explained that this testing style is sometimes referred to as “Arrange, Act, Assert” (described by Bill Wake here).
For Angular 2 classes or components that do not require dependency injection, this “Given, When, Then” or “Arrange, Act, Assert” can be used without any special test setup for Angular 2 (apart from setting up Jasmine and the spec importing described above).
For example, here’s a custom pipe function that sanitizes the time information out of dates that come back from the Twitter API. It uses a helper function to determine if the date looks like the expected format (e.g. “Tue Dec 23 04:03:33 +0000 2008”), by splitting the string on spaces and checking that there are six parts, that the fourth part contains two colons, and that the fifth part begins with a plus sign. If the helper function returns true, the pipe function splices out the fourth and fifth parts (the time information) and joins the date information back together on spaces. If the “isExpectedDateFormat” returns false, the pipe transform returns “Not Available” for the date.
Here’s an example of using the Jasmine testing framework to set up a test for this class in the “Given, When, Then” style:
You can see, though, that the first line of setup for every test (“let pipe = new TwitterDatePipe()”) will be the same. Jasmine provides some helpers for such cases to prevent unnecessary repetition. The example below shows use of the “beforeEach” helper, which will be run before every test inside of the describe block. Note that the pipe must first be instantiated outside of the beforeEach block in order to be accessed from within the tests because “let” variable declaration in Typescript uses block scope (as explained here). There’s also an “afterEach” helper that, as the name suggests, runs after each test.
In the example below I’ve also stopped instantiating the “sanitizedDate” variable, and moved the “when” portion directly into the assertion statement. However, you can see that the three distinct “given, when, then” parts still remain, just a bit more concisely written.
Here’s what it looks like when a test fails:
Jasmine makes it impossible to miss a failing spec. The lines of the describe and it blocks quickly direct you to the test that is failing. However, I haven’t found the stack traces produced by Jasmine/Angular 2 to be especially helpful. When I find a test is failing, I often begin by challenging my assumptions with console.log statements.
A Few Jasmine Tricks:
- Skipping Tests: If you want to temporarily skip a test, you can put an “x” in front of the “it” statement:
2.) Randomizing spec order and other options: In the left of the browser window, there’s an “Options” button that allows you to run the tests in a randomized order (useful to ensure that tests that shouldn’t depend on particular state aren’t coincidentally passing because they are called in a certain order).
3.) Utilizing the this keyword: Another way to use the beforeEach and afterEach helpers for eliminating code repetition is in conjunction with the “this” keyword, which is an empty object shared between the beforeEach, it, and afterEach for each test. That is, in the Jasmine doc’s words: “Each spec’s beforeEach/it/afterEach has the this as the same empty object that is set back to empty for the next spec’s beforeEach/it/afterEach.”
This means that the example above could be re-written using the “this” keyword as follows:
In my next blog post, I plan to walk through some examples of using Jasmine with Angular 2 code that relies upon dependency injection of other components. This makes the spec setup a bit more complicated and also requires the use of the Angular 2 testing helper library.
While I’ve tried to provide a brief but hopefully useful introduction to some of the Jasmine capabilities that I’ve personally found most productive, there’s a lot more that can be done with built-in Jasmine functionality alone. For me, one of the best ways to learn about Jasmine has been through exploring the side-by-side documentation/examples on the Jasmine website here.