In Part 1 of this series, we looked at how to set up Karma and Jasmine, and wrote our first test.
If you haven’t done much or any testing until now, Jasmine’s syntax can look a little strange. There’s nested describe
, it
, beforeEach
blocks, and those expect
matchers…
And then Angular heaps more syntax on top of that!
In order to get confident and fast at writing tests in your own app, it’ll help to have an overview of these functions.
You don’t have to memorize them all immediately – look them up when you need them – but you’ll probably find over time that you’ll naturally start to remember them all as you use them more.
Here are the ones you’ll use most often.
Jasmine Functions
Jasmine’s core functions describe
and it
make up the heart of your tests. They’re meant to read line a sentence – describe("isUserLoggedIn") ... it("should return true when the user is logged in")
.
Sometimes adhering to this sentence-structure idea works easily, and other times it gets in the way. Don’t worry about it too much.
describe
describe("object name or feature", function() {
});
describe
wraps a block of related tests. It takes a descriptive name, and a function that executes when your tests run.
It’s common to put the name of the object or function you’re testing, like describe("userService")
. The describe
blocks can be nested, too – for instance, your userService
could have “logged in” and “logged out” states:
describe("userService", function() {
describe("when logged in", function() {
});
describe("when logged out", function() {
});
});
beforeEach
beforeEach
sets up preconditions, and will run before each and every test in its block. It takes a function, and is meant to be used inside describe
blocks – it should be a direct child of a describe
.
This is the place where you’d create or re-initialize any objects that you need to test.
describe("a counter", function() {
var counter;
beforeEach(function() {
counter = 0;
});
});
it
it
creates a test. It’s meant to be read as a sentence, as in it("should increment by one", ...)
. it
takes a descriptive name and a function to run, and it should be nested as a direct child of a describe
block.
The test count that Karma displays when you run karma start
is based on how many it
blocks you have.
describe("a counter", function() {
var counter;
beforeEach(function() {
counter = 0;
});
it("should increment by one", function() {
counter++;
});
});
expect
expect
is a Jasmine expectation, and is meant to be used inside an it
block. It allows you to make assertions. If any assertions in a test fail, the test will fail. If a test has no assertions in it, it will pass automatically.
It’s generally a good idea to have one assertion per test. In other words, one expect
inside each it
block. If you find yourself adding lots of expectations (assertions) to a single test, you might want to break that test up into a few tests.
That said, sometimes you want to check the value of something before AND after, to make sure it changed. Breaking the “rule” of one-assertion-per-test is fine in those cases.
Here’s that counter example again:
describe("a counter", function() {
var counter;
beforeEach(function() {
counter = 0;
});
it("should increment by one", function() {
counter++;
expect(counter).toEqual(1);
});
});
.toEqual
.toEqual
is a Jasmine matcher. There are a bunch of built-in ones, covering string
s, object equality, and regular expressions, to name a few. If you sign up for my newsletter, you'll get a printable PDF cheat sheet full of Jasmine matchers and spy syntax (we'll cover spies later on).
The matchers are chained off the expect()
call, as in the example above.
Angular Test Functions
There are a couple functions you’ll need to use to test your Angular code. These are provided by the angular-mocks
module (as we saw in the last post).
module
module
loads an Angular module by name. If you need to load multiple modules, you can have multiple beforeEach(module(...))
lines. (But if you’re loading multiple modules, you might be testing too much at once.)
It’s generally used inside a beforeEach
. Notice that you don’t have to specify a function – module
returns one.
describe("userService", function() {
beforeEach(module("myapp.services.user"));
});
inject
inject
wraps a function that will get injected by Angular’s dependency injector. It works the same as with any other injectable object in Angular, but it has the added feature where you can optionally surround arguments with underscores, and it will inject them properly. This is handy, because you can name your variables the same as your services without naming conflicts.
describe("userService", function() {
var userService;
beforeEach(inject(function(_userService_, $rootScope, $q) {
userService = _userService_;
}));
});
What’s Next?
Now you’ve got a good understanding of the building blocks of an Angular test. The best way to learn these concepts is to practice them. Try writing some tests for your own app.
In Part 3, we’ll look at Testing Recipes that you can apply to different situations in Angular: how to test controllers, service, and directives… how to deal with promises… mocking services, spying on function calls, lots of fun stuff.
To be sure you don’t miss it, sign up for my newsletter over on my site. Thanks for reading!
Angular Testing Part 2: Jasmine Syntax was originally published by Dave Ceddia at Angularity on January 03, 2016.
CodeProject