Test driven development (or TDD), is a JavaScript development paradigm that espouses not only building tests to make sure your code works properly, but actually writing those tests before you code. The workflow looks something like this:
- Conceptualize project.
- Build tests for projects.
- Watch tests fail.
- Make tests not fail.
- Improve project.
- Repeat.
In this paradigm, the tests essentially serve as a rough outline for your code. Writing them first forces you to really think through what it is you want your code to do, and how it is you will actually do it. It can be a heady process, but once you are done, the benefits are self-evident. Afterwards, you just code to the test, filling in each piece as needed to make tests pass. And later when you are adding features, you will instantly know that you broke your code.
So how can we make writing tests as painless as possible? Thankfully are a variety of testing libraries out there to help simplify and streamline the process. Today, we’re going to talk about one called Jasmine.
Getting Started
As you might expect, Jasmine has an npm module which you can install easily enough through your command line:
npm install -g jasmine
Afterwards, you’ll want to head to your project’s directory and type:
jasmine init
And if you’d like you can even provision yourself with some example tests using:
jasmine examples
Finally, if you’d like to configure your project’s testing suite at all, you fill find a config file in spec/support/jasmine.json
, and you can actually run your tests by going to your project’s root directory and typing:
jasmine
Writing a Test
Jasmine tests are built on fairly simple syntax, designed to look more or less like english. Well, english with a lot of extra dots and anoymous functions.
Say hello to the describe
block. This is the broadest unit of Jasmine tests. It designates a collection of tests all pertaining to a particular part of your code. There is no hard and fast rule about how much of your code any given describe block should cover, just think of it as a sub-heading to help organize your tests.
If describe
is a sub-heading, than the it
block is a bullet point. These are used for each individual nuggets of functionality you want to test. What do you want your code to do? List each thing you come up with in an it
block. You’ll notice that the code for each of these is very similar. They are functions which take two parameters, a string description, followed by an anonymous function that contains what will actually be run.
And now with an expect
statement, our test is complete! If an it
block represents the individual qualities being tested, the expect
statements are the actual stress tests we are subjecting those qualities to. If any expect
statement were to fail in testing, the enclosing it
block will be show up as failed (and even print a convenient explanation for the failure). Specifically, expect
statements work by comparing the argument passed in the expect
call to the argumant passed in the “matcher”, in this case toEqual
. There are a variety of matchers (described below), and they are all designed to read like plain english.
Note that there is a fair amount of freedom to how you mix and match your describe
, it
, and expect
statements. describe
blocks can contain other describe
blocks, and it
blocks can be nested at the same level as those describes. But you would never place a describe
block within an it
block, and an it
block must be contrained within at least one describe
. Additionally, you can stack as many expect
statements as you like within an it
. Every expect
must succeed for the test to pass.
Before and After
Frequently you will want to execute code before and/or after each of your tests. Rather than copying and pasting that code within every it
block, Jasmine provides you with the beforeEach
and afterEach
functions:
Here we are working with some functions and variables that we’re going to use in our tests, and we use beforeEach
to ensure they are set properly before each test. afterEach
on the other hand, will allow us to reset any changes we may have made during the test. You do not need both of these blocks, and would probably only use one in most cases, but both are available. Note that unlike describe
and it
, neither takes a descriptive string as an argument, just the anonymous function. Also note that all of JavaScript’s regular scoping rules apply. If you declare a variable in your beforeEach
block it will not be available inside your it
blocks. Instead, you must declare variables within the enclosing describe
and modify them as needed within beforeEach
.
Some Useful Matchers
As I said earlier, toEqual
is called a “matcher”, and there are a few others you can choose from. You can even write your own. In the mean time, here are some others you might find useful:
Using not
before your matcher will do just what you expect: expect things not to match.
toBe
is very similar to toEqual
, but is much stricter when it comes to objects. toEqual
will actually recursively examine two different objects to see if they are “equivalent”. For example expect({a: 1}).toEqual({a: 1})
will pass, but expect({a: 1}).toBe({a: 1})
will fail. Although the two objects are equivalent, they do not actually refer to the same spot in memory and so are not strictly equal. For primitive data types there is no difference between toBe
and toEqual
.
As you might expect, toBeDefined
and toBeUndefined
test whether or not a thing is undefined
.
Also pretty straightforward. Tests whether the given argument is truthy or falsey.
toContain
is a nifty little matcher, which will check an array or string to see if it contains a given item or substring.
toBeCloseTo
allows you to round numbers to a specified decimal place. The above example passes becasue 3.14 equals 3.1 when rounded to the first decimal place. In contrast, expect(3.14).toBeCloseTo(3.1, 2)
would fail. Using a zero (i.e. toBeCloseTo(n, 0)
) will effectively round your arguments to whole integers, while negative numbers start looking at the tens places to the left of the decimal: expect(120).toBeCloseTo(100, -2)
.
There are a handful of other matchers out there as well, which you can find explanations of in Jasmine’s documentation, including toBeNull
, toBeGreaterThan
, toBeLessThan
, toThrow
(for exceptions), and toMatch
(for regex, i.e. regular expressions).
Now Go Write Some Tests!
Test driven development is the basis for smart, focused, effecient coding. It allows you to spend less time wondering what to do next and helps you avoid project-breaking bugs before they begin. All the tools you need to build these tests are easy and available. So what you waiting for?