60 minutes
Here are links to lessons that should be completed before this lesson:
Learn a commonly used testing tool.
Jasmine is a Behavior-Driven Development testing framework for JavaScript. It does not rely on browsers, DOM, or any JavaScript framework. Thus it’s suited for websites, Node.js projects, or anywhere that JavaScript can run.(stackshare.io)
Which companies use Jasmine testing?
Participants will be able to:
expect
& matchers like toBe
, toContain
, and toBeDefined
Jasmine is a behavior-driven development (BDD) framework for testing JavaScript code. (Note: BDD is a specific style of testing that tests the behavior of the code from the user’s perspective, rather than testing implementation details. It is often used with TDD! Learn more.) Jasmine has no external dependencies and does not require a DOM, which means that it’s good for getting tests up and running quickly.
As we learned in the last lesson on test-driven development (TDD), one way to ensure that your code is well-tested is to start by writing a test for the behavior you want, watch it fail, and finally write the code to make it pass (the Red-Green-Refactor pattern). Though working in a TDD style may feel slower at first, it can save you time in the long run by ensuring that your code won’t break. We’ll be working in a TDD style through this lesson.
Let’s get started by setting up a new project with Jasmine tests.
Create a new project
mkdir jasmine-practice
- create a folder for your new projectcd jasmine-practice
- change directories to that folderInstall Jasmine
npm install --global jasmine
Initialize Project
jasmine init
/spec
directory in your project! This is where your tests will go.Create Files
/spec
folder that end with “.spec.js” so that Jasmine knows which files are the test files.spec/string.spec.js
.string.spec.js
, because we’ll be testing some string functionality! In general, try to name your test files for the behavior that they’re testing.Start Test
jasmine
from the command line to run your tests! Since we haven’t added any tests yet, you’ll see something like this:Started
No specs found
Finished in 0.004 seconds
Incomplete: No specs found
Jasmine Syntax
string.spec.js
file:describe('A string', function() {
it('containing 4 letters should have length 4', function() {
WORD = 'word';
expect(WORD.length == 4).toBe(true);
});
});
describe
, provides context for a group of tests. describe
is a function that takes 2 arguments (“STRING”, FUNCTION(){}). The “STRING” should describe the context for what we are testing, and the “FUNCTION” will contain one or more tests.describe
, you can add multiple it
statements. Each it
will contain tests for a specific behavior (also known as “specs”).expect
statement within the it
.
"word".length == 4
evaluates to true
!describe
and it
statements together, they form the sentence “A string that contains 4 letters should have length 4”. It’s good practice to write Jasmine tests that read like sentences and clearly state what they are trying to test.jasmine
in the command line. You should see something like this:Started
.
1 spec, 0 failures
Finished in 0.006 seconds
WORD
to something with 5 letters, e.g. WORD = "words"
. You’ll now see something like this:Started
F
Failures:
1) A string containing 4 letters should have length 4
Message:
Expected false to be true.
Stack:
Error: Expected false to be true.
at <Jasmine>
at UserContext.<anonymous> (/path_to_project/jasmine-practice/spec/string.spec.js:4:34)
at <Jasmine>
1 spec, 1 failure
toBe
.Adding more tests
it
statement.describe('A string', function() {
it('containing 4 letters should have length 4', function() {
WORD = 'word';
expect(WORD.length == 4).toBe(true);
});
// New spec!
it('should be equal to an identical string', function() {
WORD = 'word';
expect(WORD == 'word').toBe(true);
});
});
WORD
twice. Multiple it
statements can use the same variables if they are declared under the describe
scope.describe('A string', function() {
let WORD = 'word';
it('containing 4 letters should have length 4', function() {
expect(WORD.length == 4).toBe(true);
});
it('should be equal to an identical string', function() {
expect(WORD == 'word').toBe(true);
});
});
Other matchers
toBe
, which tests that the actual value in the expect
evaluates to the expected value. However, Jasmine provides a lot of different matchers that can help us test different behaviors. These matchers can also help by printing out more specific error messages when tests fail. Check out the documentation: https://jasmine.github.io/api/3.5/matcherstoBeGreaterThan
matcher.describe('A string', function() {
let WORD = 'word';
// ... previous tests
// New test
it('should be more than 5 characters long', function() {
expect(WORD.length).toBeGreaterThan(5);
});
});
jasmine
, we now get the failure message:Failures:
1) A string should have a length greater than 5
Message:
Expected 4 to be greater than 5.
Stack:
Error: Expected 4 to be greater than 5.
at <Jasmine>
at UserContext.<anonymous> (/Users/brookeangel/Code/jasmine-practice/spec/string.spec.js:13:27)
at <Jasmine>
WORD.length
is 4 - let’s provide a different value so that our test passes:describe('A string', function() {
// ... previous tests
it('should have a length greater than 5', function() {
expect('elephant'.length).toBeGreaterThan(5);
});
});
Including modules in Jasmine tests
.spec.js
file, so you’ll want to import it modules into your spec file.module.exports
in them.// src/myFunction.js
function myFunction() {}
module.exports = myFunction;
// spec/myFunction.spec.js
const myFunction = require('../src/myFunction');
Local Installation
npm init --yes
- Makes a package.json
file, for projects that don’t already have one.npm install --save-dev jasmine
will save Jasmine locally in your current project. Notice that this creates a package-lock.json
file in the project. You don’t need to understand everything in this file - just know that it specifies exactly which verion of Jasmine you downloaded to the project.jasmine init
. With a local installation, you can initialize Jasmine by running node node_modules/jasmine/bin/jasmine init
. (We don’t have to run this for our project, since Jasmine is already initialized.)"test": "jasmine"
jasmine
from the command line, run npm test
to run all the specs.expect
to every it
function, or you could end up with this false positive.expect
inside of asynchronous code is ignored, therefore passing. (false positive)
done
- this signals to the test engine this is asynchronous code and it must wait. Learn more.Remember the Basic JavaScript practice that we completed a few lessons ago? We’re going to rewrite a few of those functions, TDD style!
FizzBuzz (from basic JS practice, challenge 4)
We’re going to TDD a slight twist on fizzBuzz
. The function will:
/spec
directory named fizzBuzz.spec.js
. Our tests will go here.fizzBuzz.spec.js
, add a describe
to add some context.fizzBuzz
is defined.
it
within the describe. Make sure it states what you are testing.toBeDefined
. See documentation.npm test
. If all goes well, you should see the following failure:1) fizzBuzz should be defined
Message:
ReferenceError: fizzBuzz is not defined
// spec/fizzBuzz.spec.js
describe("fizzBuzz", function(){
it("should be defined", function(){
expect(fizzBuzz).toBeDefined();
});
});
/src
in the project, and make a fizzBuzz.js
file within the directory. Add a fizzBuzz function to the file, and export it from the file so that we can import it in our test. Don’t add any functionality to fizzBuzz
just yet! All that our function has to do to make the test pass is “be defined”.fizzBuzz
function into the spec file. (Hint: see the “Including modules in Jasmine tests” section above.)
// src/fizzBuzz.js function fizzBuzz() {};
module.exports = fizzBuzz;
// spec/fizzBuzz.spec.js describe(“fizzBuzz”, function(){ it(“should be defined”, function(){ expect(fizzBuzz).toBeDefined(); }); });fizzBuzz
function.
// src/fizzBuzz.js function fizzBuzz(num) { return “fizz”; };
module.exports = fizzBuzz;
// spec/fizzBuzz.spec.js describe(“fizzBuzz”, function(){ // older tests
it(“should return ‘fizz’ when given a multiple of 3”, function(){ expect(fizzBuzz(3)).toBe(“fizz”); expect(fizzBuzz(6)).toBe(“fizz”); }); });Notice that we haven’t implemented all the functionality for fizzBuzz
yet - we don’t have to for the test to pass. That means we should add more tests!
fizzBuzz
.
// src/fizzBuzz.js
function fizzBuzz(num) {
if (num % 15 === 0) {
return "fizzbuzz";
} else if (num % 3 === 0) {
return "fizz";
} else if (num % 5 === 0) {
return "buzz";
} else {
return num;
}
};
module.exports = fizzBuzz;
// spec/fizzBuzz.spec.js
const fizzBuzz = require('../src/fizzBuzz');
describe("fizzBuzz", function(){
it("should be defined", function(){
expect(fizzBuzz).toBeDefined();
});
it("should return 'fizz' when given a multiple of 3", function(){
expect(fizzBuzz(3)).toBe("fizz");
expect(fizzBuzz(6)).toBe("fizz");
});
it("should return 'buzz' when given a multiple of 5", function(){
expect(fizzBuzz(5)).toBe("buzz");
expect(fizzBuzz(10)).toBe("buzz");
});
it("should return 'fizzbuzz' when given a multiple of 3 and 5", function(){
expect(fizzBuzz(15)).toBe("fizzbuzz");
expect(fizzBuzz(30)).toBe("fizzbuzz");
});
});
Exploring new matchers:
toBeTruthy
and toBeFalsy
in your tests.mySplit
that takes a string, and returns an array of its letters, using TDD. Use the matcher toContain
in your test.
mySplit("dog") => ["d","o","g"]
Challenge 1: Read the docs on beforeEach
and afterEach
. Try to write a test that uses beforeEach
to set up tests. There’s a good example in the Jasmine Tutorial.
Challenge 2: You can also run Jasmine tests in the browser for a nicer UI! Follow the installation instructions on Jasmine’s github repo to make your tests run in the browser instead of the command line.
Question: What is Jasmine and how does it work in your project?
Exercise: Each student should pick a matcher, like toBeNull()
. Then describe that matcher to the class and how it should be used.