Chapter 4. Matchers in Depth
There are a lot of useful matchers that come with Jasmine. Later in this section, you’ll also see how to build your own.
Equality: toEqual
Perhaps the simplest matcher in Jasmine is toEqual
. It simply checks if two things are equal (and not necessarily the same exact object, as you’ll see in Chapter 5).
The following expect
functions will pass:
expect(true).toEqual(true); expect([1, 2, 3]).toEqual([1, 2, 3]); expect({}).toEqual({});
Here are some examples of toEqual
that will fail:
expect(5).toEqual(12); expect([1, 2, 3, 4]).toEqual([1, 2, 3]); expect(true).toEqual(100);
Keep in mind that this is different from the toBe
matcher. The subtle difference is noted next.
Identity: toBe
At first, the toBe
matcher looks a lot like the toEqual
matcher, but it’s not exactly the same. toBe
checks if two things are the same object, not just if they are equivalent.
Here’s an example spec that illustrates the difference between toEqual
and toBe
:
var spot = { species: "Border Collie" }; var cosmo = { species: "Border Collie" }; expect(spot).toEqual(cosmo); // success; equivalent expect(spot).toBe(cosmo); // failure; not the same object expect(spot).toBe(spot); // success; the same object
We see that, although spot
and cosmo
look really similar and are equal, they aren’t the same object. Because of that, they evaluate as equal, not the same.
The same is also true for arrays:
var arr = [1, 2, 3]; expect(arr).toEqual([1, 2, 3]); // success; equivalent expect(arr).toBe([1, 2, 3]); // failure; not the same array
You might notice that toBe
works for primitives (numbers, Booleans, strings). This is because JavaScript’s ===
operator evaluates primitives as the same entity. Using toBe
is essentially using the ===
operator.
Use toEqual
when checking the equivalence of primitive types, even if toBe
will work. Using toBe
might break your tests if you later decide to change a number to an array, for example.
For more about how this nuance works in JavaScript, see the video JavaScript Primitive Types vs Reference Types.
Yes or No? toBeTruthy, toBeFalsy
To test if something evaluates to true
, you use the toBeTruthy
matcher:
expect(true).toBeTruthy(); expect(12).toBeTruthy(); expect({}).toBeTruthy();
Likewise, to test if something evaluates to false
, you use toBeFalsy
:
expect(false).toBeFalsy(); expect(null).toBeFalsy(); expect("").toBeFalsy();
Note that Jasmine’s evaluation of truthy and falsy are identical to JavaScript’s. This means that true
is truthy, but so is "Hello world"
, or the number 12
, or an object. It’s useful to think of all the things that are falsy, and then everything else as truthy.
For reference, here’s a list of things that are falsy in Jasmine (and in JavaScript, too):
-
false
-
0
-
""
-
undefined
(note that the variableundefined
isn’t always undefined!)
-
null
-
NaN
If you haven’t seen NaN
before, it’s a special number value that stands for Not a Number. It represents nonsensical number values like 0/0
. It’s also returned by some functions that return numbers (for example, parseInt("hello")
returns NaN
because it cannot properly parse a number).
If you want to make sure something is literally true
or false
and nothing else, use the toEqual
matcher like so:
expect(myVariable).toEqual(true); expect(myOtherVariable).toEqual(false);
Negate Other Matchers with not
It’s frequently useful to reverse Jasmine’s matchers to make sure that they aren’t true
. To do that, simply prefix things with .not
:
expect(foo).not.toEqual(bar); expect("Hello planet").not.toContain("world");
Check If an Element Is Present with toContain
Sometimes you want to verify that an element is a member of an array, somewhere. To do that, you can use the toContain
matcher:
expect([1, 2, 3, 4]).toContain(3); expect(["Penguin", "Turtle", "Pig", "Duck"]).toContain("Duck");
Note that toContain
doesn’t check if the array contains the exact same object, so the following example will succeed:
var dog = { name: "Fido" }; expect([ { name: "Spike" }, { name: "Fido" }, { name: "Spot" } ]).toContain(dog);
The toContain
matcher also works in strings, as we saw in the first example of this book:
expect("Hello world").toContain("world"); expect(favoriteCandy).not.toContain("Almond");
Is It Defined? toBeDefined, toBeUndefined
As with truthiness and falsiness, there are matchers to check if something is defined or undefined.
Before we start, let’s briefly review JavaScript’s notion of undefined
and how it compares to null
: when you declare a new variable with no value specified, its type is “undefined” (just like 123
’s type is “number”). In other languages, it might be null
or nil
. Not in JavaScript! There’s a lot of confusion around this, but it doesn’t directly apply to Jasmine.
For more background on undefined
and how it works, check out Understanding JavaScript’s “undefined”.
Here are a few examples to demonstrate how these matchers work:
var somethingUndefined; expect("Hello!").toBeDefined(); // success expect(null).toBeDefined(); // success expect(somethingUndefined).toBeDefined(); // failure
var somethingElseUndefined; expect(somethingElseUndefined).toBeUndefined(); // success expect(12).toBeUndefined(); // failure expect(null).toBeUndefined(); // failure
It’s worth noting that the variables you’re checking have to be defined. The following code throws a ReferenceError
and will fail:
it("tests toBeUndefined", function() { expect(someUndefinedVariable).toBeUndefined(); // Throws a ReferenceError because someUndefinedVariable hasn't been declared. });
Nullness: toBeNull
The toBeNull
matcher is fairly straightforward. If you hadn’t guessed by now, it checks if something is null
:
expect(null).toBeNull(); // success expect(false).toBeNull(); // failure expect(somethingUndefined).toBeNull(); // failure
Fairly simple!
Is It NaN? toBeNaN
Like toBeNull
, toBeNaN
checks if something is NaN
:
expect(5).not.toBeNaN(); // success expect(0 / 0).toBeNaN(); // success expect(parseInt("hello")).toBeNaN(); // success
Note
This is different from JavaScript’s built-in isNaN
function. The built-in isNaN
will return true
for many nonnumber types, such as nonnumeric strings, objects, and arrays. Jasmine’s will be positive only if it’s the NaN
value.
Comparators: toBeGreaterThan, toBeLessThan
The toBeGreaterThan
and toBeLessThan
matchers check if something is greater than or less than something else. All of these will pass:
expect(8).toBeGreaterThan(5); expect(5).toBeLessThan(12); expect("a").toBeLessThan("z"); // Notice that it works for strings too!
Not too difficult!
Nearness: toBeCloseTo
toBeCloseTo
allows you to check if a number is close to another number, given a certain amount of decimal precision as the second argument.
If you want to make sure that a variable is close to 12.3 within one decimal point, you’d code it like this:
expect(12.34).toBeCloseTo(12.3, 1); // success
If you want it to be the same within two decimal points, you’d change the 1
to a 2
. This spec will fail, though—they differ at the second decimal digit:
expect(12.34).toBeCloseTo(12.3, 2); // failure
In this case, any second argument over 2
will fail, so the following will fail as well:
expect(12.34).toBeCloseTo(12.3, 3); // failure expect(12.34).toBeCloseTo(12.3, 4); // failure expect(12.34).toBeCloseTo(12.3, 5); // failure // and so on...
Setting the second argument to 0
effectively rounds the numbers to integers:
expect(12.3456789).toBeCloseTo(12, 0); // success expect(500).toBeCloseTo(500.087315, 0); // success expect(500.087315).toBeCloseTo(500, 0); // success
I find toBeCloseTo
a little confusing and limiting. In Custom Matchers, we’ll make a matcher that aims to improve it.
Using toMatch with Regular Expressions
toMatch
checks if something is matched, given a regular expression. It can be passed as a regular expression or a string, which is then parsed as a regular expression. All of the following will succeed:
expect("foo bar").toMatch(/bar/); expect("horse_ebooks.jpg").toMatch(/\w+.(jpg|gif|png|svg)/i); expect("jasmine@example.com").toMatch("\w+@\w+\.\w+");
For more on regular expressions in JavaScript, check out this very helpful article on the Mozilla Developer Network. (Note that these regular expression examples aren’t as thorough as they could be—a better email regular expression is very long!)
Checking If a Function Throws an Error with toThrow
toThrow
lets you express, “Hey, I expect this function to throw an error”:
var throwMeAnError = function() { throw new Error(); }; expect(throwMeAnError).toThrow();
You can use this with anonymous functions too, which can be more useful. For example, let’s say there’s a function that should throw an exception with bad input, like so:
calculate("BAD INPUT"); // This should throw some exciting exception
To test that, we use Jasmine like so:
expect(function() { calculate("BAD INPUT"); }).toThrow();
Whether you use an anonymous or named function, you still need to pass a function because Jasmine will call it when running your specs.
Custom Matchers
You can create custom matchers, too! You must add the matcher before every spec in which you want it, you must add it. To do that, we’ll be using beforeEach
, which is explained in more detail in Chapter 5.
Let’s say you want to add a matcher called toBeLarge
, which checks if a number is greater than 100. At the very top of a file (or at the top of a describe
), you can add the following:
beforeEach(function() { this.addMatchers({ toBeLarge: function() { this.message = function() { return "Expected " + this.actual + " to be large"; }; return this.actual > 100; } }); });
This requires a little bit of knowledge about how Jasmine is put together. Every matcher takes an argument to the expect
function, right? expect(200)
has 200
as its argument. This argument, in Jasmine, is this.actual
when we’re defining a new matcher; this.message
is a function that, if the matcher fails, returns the explanatory output message. Finally, we return a Boolean indicating whether this.actual
is large.
Now we can do the following in our specs:
expect(5).toBeLarge(); // failure expect(200).toBeLarge(); // success expect(12).not.toBeLarge(); // success
A more complex matcher might want to introduce this syntax as a replacement for toBeCloseTo
:
// Expect 6 to be within 2 of 5 (between 3 and 7, inclusive). expect(6).toBeWithinOf(2, 5);
This matcher will take two arguments and is otherwise similar to the previous example:
beforeEach(function() { this.addMatchers({ toBeWithinOf: function(distance, base) { this.message = function() { var lower = base - distance; var upper = base + distance; return "Expected " + this.actual + " to be between " + lower + " and " + upper + " (inclusive)"; }; return Math.abs(this.actual - base) <= distance; } }); });
In this example, we’re making sure that this.actual
is, at most, distance
away from base
. The message calculates the lower and upper bounds, and the matcher’s result is a simple bounds check.
Get JavaScript Testing with Jasmine now with the O’Reilly learning platform.
O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.