Testing with Jasmine

About Jasmine

  • Unlike Mocha, Jasmine does not require additional libraries to get started with writing tests

How it works

  • Create an html file
  • Link CSS and JavaScript tags
  • Start writing tests!

Example: Jasmine Starter

https://codepen.io/eschoppik/pen/ZybNdo?editors=1000

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Jasmine Tests</title>
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/jasmine/2.6.2/jasmine.css"></link>
</head>
<body>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/jasmine/2.6.2/jasmine.js"></script>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/jasmine/2.6.2/jasmine-html.js"></script>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/jasmine/2.6.2/boot.js"></script>
</body>
</html>

Essential Keywords (functions)

  • describe
    • "let me tell you about _"
    • definition: describe(string, callback)
  • it
    • "here's what I expect"
    • definition: it (string, callback)
    • placed inside callback function of describe function
    • referred in Jasmine as "spec" which is equivalent to "test"
  • expect
    • "let me describe __ to you."
    • placed inside callback function of it function
    • return an object which we can attach mehtods (matchers) to

Example: In Code

//describe, it, and expect are given to us by Jasmine!
var earth = {
  isRound: true,
  numberFromSun: 3
}

describe("Earth", function(){
  it("is round", function(){
    expect(earth.isRound).toBe(true)
  });
  it("is the third planet from the sun", function(){
    expect(earth.numberFromSun).toBe(3)
  });
});

Example: Matchers

toBe / not.toBe
     use === i.e. compares value + typeof
     // typeof doesn't work with an array since array is a type of object
     // does not compare object reference
toBeCloseTo
toBeDefined
toBeFalsey / toBeTruthy
toBeGreaterThan / toBeLessThan
toContain (array)
toEqual (obj & array)
     use === + compares references in memory
jasmine.any() (check type)

Example: Using === with objects

var arr1 = [1,2,3];
var arr2 = [1,2,3];
arr1 === arr2 //returns false, different references in memory

arr3 = [];
arr4 = arr3;
arr3 === arr4 //returns true, same reference in memory

Example: Matchers Check it out! http://bit.ly/2sfBOgx

describe("Jasmine Matchers", function() {
  it("allows for === and deep equality", function() {
    expect(1+1).toBe(2);
    expect([1,2,3]).toEqual([1,2,3]);
  });

  it("allows for easy precision checking", function() {
    expect(3.1415).toBeCloseTo(3.14,2);
  });

  it("allows for easy truthy / falsey checking", function() {
    expect(0).toBeFalsy();
    expect([]).toBeTruthy();
  });
  it("allows for checking contents of an object", function() {
    expect([1,2,3]).toContain(1);
    expect({name:'Elie'}).toEqual(jasmine.objectContaining({name:'Elie'}));
  });
  it("allows for easy type checking", function() {
    expect([]).toEqual(jasmine.any(Array));
    expect(function(){}).toEqual(jasmine.any(Function));
  });
});

Hook: beforeEach

  • without beforeEach: define arr variable 3 times!
describe("#push", function(){
  it("adds elements to an array", function(){
      var arr = [1,3,5];
      arr.push(7);
      expect(arr).toEqual([1,3,5,7]);
  });

  it("returns the new length of the array", function(){
      var arr = [1,3,5];
      expect(arr.push(7)).toBe(4);
  });

  it("adds anything into the array", function(){
      var arr = [1,3,5];
      expect(arr.push({})).toBe(4);
  });
});

Use beforeEach: run before each "it" callback

describe("Arrays", function(){
  var arr;
  beforeEach(function(){
    arr = [1,3,5];
  });
  it("adds elements to an array", function(){
    arr.push(7);
    expect(arr).toEqual([1,3,5,7]);
  });

  it("returns the new length of the array", function(){
    expect(arr.push(7)).toBe(4);
  });

  it("adds anything into the array", function(){
    expect(arr.push({})).toBe(4);
  });
});

Hook: afterEach

  • run after each "it" callback - useful for teardown
  • Teardown test is commonly used with databases to ensure that the database is the same before and after the test
describe("Counting", function(){
  var count = 0;

  beforeEach(function(){
    count++;
  });

  afterEach(function(){
    count = 0;
  });

  it("has a counter that increments", function(){
    expect(count).toBe(1);
  });

  it("gets reset", function(){
    expect(count).toBe(1);
  });
});

Hook: beforeAll / afterAll

  • run before/after all tests! Does not reset in between
var arr = [];
beforeAll(function(){
  arr = [1,2,3];
})
describe("Counting", function(){
  it("starts with an array", function(){
    arr.push(4);
    expect(1).toBe(1);
  });
  it("keeps mutating that array", function(){
    console.log(arr); // [1,2,3,4]
    arr.push(5);
    expect(1).toBe(1);
  });
});
describe("Again", function(){
  it("keeps mutating the array...again", function(){
    console.log(arr); // [1,2,3,4,5]
    expect(1).toBe(1);
  });
});

Nesting describe

  • Break down large topic into smaller describe blocks. Each of the describe block will cover individual array method
describe("Array", function(){
  var arr;
  beforeEach(function(){
    arr = [1,3,5];
  });

  describe("#unshift", function(){
    it("adds an element to the beginning of an array", function(){
      arr.unshift(17);
      expect(arr[0]).toBe(17);
    });
    it("returns the new length", function(){
      expect(arr.unshift(1000)).toBe(4);
    });
  });
  describe("#push", function(){
    it("adds elements to the end of an array", function(){
      arr.push(7);
      expect(arr[arr.length-1]).toBe(7);
    });
    it("returns the new length", function(){
      expect(arr.push(1000)).toBe(4);
    });
  });
});

Pending tests

describe("Pending specs", function(){
  xit("can start with an xit", function(){
    expect(true).toBe(true);
  });

  it("is a pending test if there is no callback function");

  it("is pending if the pending function is invoked inside the callback", function(){
    expect(2).toBe(2);
    pending();
  });
});

Example: How many expect function to use on each "it" block?

Not great

describe("Earth", function(){
    it('is round and has a method to check what number it is from the sun', function(){
        expect(earth.isRound()).toBe(true);
        expect(earth.howFarFromSun).toBe(jasmine.any(Function);
        expect(earth.howFarFromSun()).toBe(3);
    });
});

Better

describe("Earth", function(){
    it('is round', function(){
        expect(earth.isRound()).toBe(true);
    });
    it('has a method to check what number it is from the sun', function(){
        expect(earth.howFarFromSun).toBe(jasmine.any(Function);
        expect(earth.howFarFromSun()).toBe(3);
    });
});

Spies

  • Jasmine has test double functions called spies.
  • A spy can stub (mimic) any function and track calls to it and all arguments.
  • Spies only exists in the describe or it block in which it is defined,
  • Spies are removed after each spec.
  • There are special matchers for interacting with spies.

Clock

  • The Jasmine Clock is available for testing time dependent code.
  • It is installed by invoking jasmine.clock().install()
  • Be sure to uninstall the clock after you are done to restore the original functions.

Testing async code

  • Jasmine also has support for running specs that require testing async code
  • beforeAll, afterAll, beforeEach, afterEach, and it take an optional single argument (commonly called 'done') that should be called when the async work is complete.
  • A test will not complete until its 'done' is called.
  • Jasmine will wait 5 seconds for the done callback to run or the test will timeout.
  • you can modify the internal timer with jasmine.DEFAULT_TIMEOUT_INTERVAL

Different ways we can test our codes

  • TDD - Test Driven Development
  • 1) Write the tests
  • 2) See the tests fail (RED)
  • 3) Write code to pass the tests (GREEN)
  • 4) Refactor code as necessary (REFACTOR)
  • 5) Repeat

BDD - Behavior Driven Development

  • A subset of TDD
  • Not mutually exclusive with TDD
  • Jasmine is a BDD style framework (describe, it, expect)
  • Involves being verbose with our style and describing the behavior of the functionality
  • Helpful when testing the design of the software

Other kinds of tests

  • Unit tests
  • Integration tests
  • Acceptance tests
  • Stress tests

Recap

  • Unit testing involves testing pieces of functionality
  • Jasmine is a testing framework that allows us to easily write unit tests
  • Jasmine has quite a few matchers for testing almost any kind of expectation
  • Using beforeEach / afterEach / beforeAll / afterAll hooks can help reduce duplication and confusion (DRY)
  • Jasmine provides spies for mimicking the behavior of a function
  • Jasmine provides a clock object for testing timers and a callback function for testing asynchronous code
  • Unit testing is just one part of testing applications