JS401

Intro to Unit Testing with Jasmine

Slides: http://www.teaching-materials.org/jasmine

Testing Overview


Unit Tests

Unit Tests verify that a relatively small piece of code is doing what it is intended to do. They are narrow in scope and do not check outside systems.


This workshop focuses on Unit Tests

Testing Overview


Integration Tests

Integration Tests – demonstrate that different pieces of the system work together.

Testing Overview


QA Tests

QA Tests – product-oriented tests that focus on identifying "bugs" in software from the perspective of users. May be automated or manual.

Testing Overview


Regression Tests

Regression Tests – product-oriented tests that make sure that new changes do not break mission-critical functionality that has already been built (logging in/out, checkout out).

Why test?

A Common Practice


You may be familiar with the following steps:

  1. Write code
  2. Manually test outcomes


This can be useful when you're trying to learn the basics, move quickly, and prototype.

A Common Perception


Sometimes, it can feel "slower" to write tests.

But consider...


Projects can live a long time

Code bases can get big

Developers come and go

Time required for manually testing adds up

"upgrades" and "bug fixes" can break other features

Code is for humans

(and so are the bugs)

  • set expectations
  • think through the steps
  • break down the pieces
  • ensure ease of upgrades and deprecations

Benefits of testing

  • shorten feedback loop
  • prevent regressions
  • document code

What to test

  • inputs/outputs – when something goes into a function, what should come out?
  • "side effects" – when a function executes, what will change outside of the function?

window.bees = 200;

function collectHoney(honeyBucket){ // input honeyBucket
	window.bees = 0; // side effect occurs outside function
	return honeyBucket++; // output
};
					

What is TDD?


Test-driven Development (TDD) is an approach to programming.


  1. Write an automated (failing) test. This test will describe the desired behavior
  2. Write a small amount of code that will make the test pass
  3. Repeat

A Note to the Purists

TDD recommends tests first before any code.


But it's good to remember the reason: maintainable, understandable, reliable code.


The most important thing is that you write tests – regardless of whether you choose to do so before, during, or after coding.

What is BDD?


Behavior-driven Development (BDD) emerged out of TDD as an approach to programming that considers both business and technical needs.


From the developer perspective, BDD is a style of TDD that uses English in a natural way to describe expected behavior.

BDD patterns


In this workshop, we will be using the following pattern:

Describe "a part of an application"
	It "should do something specific"


So you might have something like this:

Describe "a cat trampoline"
	It "should have a place for a cat to jump"
	It "should not be large enough for a person"
	It "should make cats happy"

Exercise time


Click here for directions

Suggested time: 10 minutes

Jasmine

What is Jasmine?


Jasmine is a behavior-driven development framework for testing JavaScript code. It does not depend on any other JavaScript frameworks.

Jasmine 2.0 Docs

Terminology


Spec

A spec is short for "specification". This is a building block in Jasmine that describes a certain behavior precisely


Suite

A suite is a grouping of specs

Setting up the Spec Runner

In this workshop, we'll be running our tests in a browser from a file called SpecRunner.html. That means, we need to make sure our files are loaded before we write tests. Here's how:

<!-- include jasmine library -->
<link rel="stylesheet" type="text/css" href="lib/jasmine.css">
<script type="text/javascript" src="lib/jasmine.js"></script>
<script type="text/javascript" src="lib/jasmine-html.js"></script>
<script type="text/javascript" src="lib/boot.js"></script>

<!-- include source files here... -->
<script type="text/javascript" src="src/App.js"></script>
<script type="text/javascript" src="src/Game.js"></script>

<!-- include spec files here... -->
<script type="text/javascript" src="spec/GameSpec.js"></script>

Suites, specs, and matchers

// Suites start with the keyword "describe"
describe("A suite", function(){

  // Specs start with the keyword "it"
  it("contains spec with an expectation", function(){

    // expect() statements have matchers like toBe()
    expect(true).toBe(true);

  });
});

toBe() or not.toBe()

There are special functions called matchers that will compare an actual value with an expected value.


You can also add a .not() to your matcher:

expect(true).not.toBe(false);

Here are some other matchers that are built-in:

.toBeDefined();
.toEqual(); // strict equality
.toMatch(); // for regular expressions
.toContain(); // find an item in an array

A pattern for executing tests


  • Set up the stage
  • Let the players play their parts
  • Tear down the stage

Set up and Tear down

Often, you'll run tests with the same set up and tear down procedures. For that, we can use the beforeEach() and afterEach() functions:

describe("a self-driving car", function(){
  describe("turning in traffic", function(){
    beforeEach(function(){
      startEngine();
      navigate("Safeway");
    });

    afterEach(function(){
      navigate("Home");
      killEngine();
    });

    it("should signal when turning", function(){
      turnLeft();
      expect(leftSignalPosition).toEqual("on");
    });
  });
});
					

more from the docs

Exercise time


Click here for directions

Suggested time: 30 minutes

Control Flow


Control Flow is the order in which the individual statements, instructions or function calls are executed or evaluated.

Control Flow Example

Filtering, Sorting, and Rendering

Control Flow Example

In words


Describe "a book collection"
  It should sort based on the "sort by" field
  It should sort by author's last name if "sort by" field is empty
  It should call sort whenever a user selects "filter by"
  It should re-render the collection after sort

Testing Control Flow


Control flow can get complicated, and it becomes tricky to test all the possible scenarios manually.


Therefore, it's useful to break up the pieces and write unit tests for each piece.

Terminology


Stub

A stub is a piece of code used to stand in for some other functionality

Spies


Spies can test the control flow of an application by:


  • stubbing (standing in for) a function
  • tracking calls to a function
  • exposing arguments that a function was called with

Using Spies

  • spies are usually called on Object methods
  • spies have their own matchers such as toHaveBeenCalled(), toHaveBeenCalledWith(), and more!
  • describe("a robot sidewalk sweeper", function(){
      it("should look left before crossing the street", function(){
        spyOn(robot, 'lookLeft'); // set up the spy
        robot.crossStreet(); // execute
    
        // use matchers to see if the function was called
        expect(robot.lookLeft).toHaveBeenCalled();
        expect(robot.lookLeft.calls.count()).toBeGreaterThan(1);
      });
    });

Tracking Spies


Function calls to spies are tracked through the calls property.

expect(robot.lookLeft.calls.count()).toBeGreaterThan(1);

Read more about it here!

Exercise time


Click here for directions

Suggested time: 45 minutes

Automation

  • Running your tests in the browser is helpful as you're developing, but automation will help you run all of the project's tests
  • Automation is useful as part of the "build" or "continuous integration" process – that means that code needs to pass all the tests before a merge
  • Automation tooling is particular to your technology stack and deploy process

Fixtures


If you find yourself wanting to use a specific, known set of data repeatedly (especially across different tests), you may want to try using a fixture.


A fixture is a way to consistently represent and load data for your tests. You'll need to find a plugin to use with Jasmine.

Conclusion

  • Some things are hard to test, so get creative
  • Practice, practice, practice