Last week, we started strolling through Jasmine and seeing what it had to offer us with regards to unit testing. Today, we’ll be finishing that up with some of the more advanced features included with Jasmine so that you can see the whole package and get started unit testing your own JavaScript. Trust me, even its advanced features are simple to use, so there shouldn’t be anything holding you back from reading this and getting started doing your own unit testing.
Spies and Mocks
We’ll start this one off with spies. Spies are really cool and take advantage of JavaScript’s dynamic nature to allow you to get some interesting metadata about what is happening behind the scenes in some objects. For instance, if you’re testing a function that takes a callback argument, you might want to be certain that the callback was indeed called properly. You can spy on the callback method to see if it was called and even what arguments it was called with and how many times it was called. Take a look below to see all the really cool things you get from using spyOn
, the method you call to spy on a function. This code is taken directly from the Jasmine documentation.
1 | describe("A spy", function() { |
It’s simple to use spyOn
; just pass it an object, and the name of a method on that object that you want to spy on. If you look closely, you might realize that spyOn
is replacing the original function with a spy that intercepts the function calls and tracks a lot of potentially useful information about them. The problem we run into above is that once we’ve replaced the original function, we’ve lost its capabilities. We can remedy that with andCallThrough
. If you chain andCallThrough()
after calling spyOn
, the spy will then pass any calls to it through to the original function. Here’s another bit of code from the docs to show off andCallThrough
:
1 | describe("A spy, when configured to call through", function() { |
Sometimes you don’t want it to call through to the original. Maybe you just want the spy to return a specific value so that you can test to see what happens when that value is returned. Or maybe you just want it to return a single value for consistency’s sake. Well, you can tell a spy to return a specified value with andReturn
. It’s used similarly to andCallThrough
, but obviously it is used to return a specific value instead of calling through to the original function. It takes a single argument, which is the value to be returned.
1 | describe("A spy, when faking a return value", function() { |
For the final andXxx
spy method, we have andCallfake
, which will take a function argument. Rather than passing through to the original function, this method will make it so that the spy passes through to call the function that you specified as its argument. It’ll even return any values returned from your new fake function.
1 | describe("A spy, when faking a return value", function() { |
Now, you might be wondering, what if I don’t have an object already that I want the spy to work with? I just want to create a spy without any existing objects or functions. Is this possible? You bet! First, let’s take a look at how to create a spy function from thin air, then we’ll move on to explore the idea of making an entire spy object.
You make a spy function with jasmine.createSpy
and you pass in a name. It’ll return the spy function for you. The name seems a bit useless because it isn’t used as an identifier that we can refer to it as, but as you can see below, it can be used with the spies identity
property in error messages to specify where an error occurred. Here it is:
1 | describe("A spy, when created manually", function() { |
Finally, let’s create an object with all spy methods using jasmine.createSpyObj
. As with createSpy
, it takes a name, but it also takes an array of strings that will be used as the names of the spy functions attached to the object. The name is used the exact same way that it is used with createSpy
: identifying objects during Jasmine error results.
1 | describe("Multiple spies, when created manually", function() { |
Testing Asynchronous Functions
Asynchronous programming isn’t simple, at least not as simple as straight-forward synchronous programming. This makes people scared to test asynchronous functions even more, but Jasmine makes it really simple to test asynchronous functions too. Let’s take a look at an example using an AJAX request with jQuery:
1 | describe("Asynchronous Tests", function() { |
This might not make much sense just looking at it, but with a little explanation it’ll seem dead simple and all your fears of asynchronous testing will dissipate. We’ll hop right into the body of the it
block to get started. First we created a couple flags. These aren’t always necessary, depending on how the asynchronous function works, but if you need them, these can hold Booleans that specify whether the asynchronous function worked/finished, like I did here. Now we get to the fun part: runs
and waitsFor
. The first call to runs
is where we run an asynchronous function. Then we use waitsFor
to determine when/if the asynchronous function finished. This is done by specifying a function that returns a boolean that should be true when the asynchronous work is finished or false before it finishes. This is the first argument passed in. The next one is the error we want to show if it never returns true, and the final argument is the number of milliseconds we should wait before it times out and fails the spec. The function that is passed into waitsFor
is run on short intervals until it either returns true or it times out. Then we move on and run the function passed into the next runs
call. This is generally where you do your expect
ing.
The fun part is that you can continue alternating between runs
and waitsfor
(potentially) inifinitely. So, if you want to run another asynchronous function in the second runs
and then do another waitsfor
and finally call runs
once again to complete your tests, it’s entirely possible. You’ll see me do this in an article soon when I talk about testing Socket.IO.
Mocking the JavaScript Clock
If you have code that runs with setTimeout
or setInterval
, you can skip the asynchronous testing and just use Jasmine to control the clock, allowing you to run that code synchronously. Just tell jasmine to use its own mock clock with jasmine.Clock.useMock()
and then use jasmine.Clock.tick([number])
to move the clock ahead whenever you want.
1 | describe("Manually ticking the Jasmine Mock Clock", function() { |
As simple as the asynchronous testing is, I still would rather use this when I can. It’s fun to have that much power. Of course, this doesn’t actually affect the clock, but who cares? It feels like it does, right?
Matching Types with jasmine.any
Sometimes, trying to test for a specific value is too strict and you just want to make sure it is of a specific type, like a number or object. In this case jasmine.any
comes to the rescue. You can use it in any matcher to check a value’s type instead of comparing it to an exact value.
1 | describe("jasmine.any", function() { |
It takes a constructor name and compares it to the constructor of the value. This means, you can test it against your custom types too, not just the built in ones.
Disabling Specs and Suites
Sometimes you don’t want a spec or suite to run, whether it is because it takes too long, or you know it will fail and don’t want to deal with it until later. You could always comment it out, but then if you want to turn all of the commented out specs back on, it’s difficult to do a search and replace. Instead you can prepend describe
or it
with an “x”, and the suite or spec will be skipped just as if it was commented out, but a simple search for xdescribe
can be replaced with describe
. The same goes for xit
and it
.
1 | xdescribe("A disabled suite or spec", function() { |
Conclusion
Well that’s pretty much all you need to know to get started with unit testing using the Jasmine framework. I hope that its simplicity will draw you in and that if you’ve been holding off on unit testing, you’ll start now. God bless and happy coding.