Testing JavaScript Applications

by Alexander Gerasimov and Dmitrey Gerasimov

¿Por qué?

¡Porque!

  • refactoring
  • organization, modularization, extensibility
  • documentation
  • defect prevention
  • collaboration

"Any feature without a test doesn’t exist"

Steve Loughran HP Laboratories

TDD & BDD

Tests first

red/green/refactor

Red: Write a failing test

red/green/refactor

Green: Make it pass

red/green/refactor

Refactor: Eliminate redundancy

Structure

  • Setup: Put the Unit Under Test (UUT) or the overall test system in the state needed to run the test.
  • Execution: Trigger/drive the UUT to perform the target behavior and capture all output, such as return values and output parameters.
  • Validation: Ensure the results of the test are correct.
  • Cleanup: Restore the UUT or the overall test system to the pre-test state.
http://en.wikipedia.org/wiki/Test-driven_development#Test_structure

TDD assertions


var assert = chai.assert;
assert.typeOf(foo, 'string');
assert.equal(foo, 'bar');
assert.lengthOf(foo, 3)
assert.property(tea, 'favors');
assert.lengthOf(tea.flavors, 3);
						

BDD

Behavior-driven development

given-when-then

Story: Returns go to stock
In order to keep track of stock
As a store owner
I want to add items back to stock when they're returned

Scenario 1: Refunded items should be returned to stock
Given a customer previously bought a black sweater from me
And I currently have three black sweaters left in stock
When he returns the sweater for a refund
Then I should have four black sweaters in stock

Cucumber

Feature: Addition
  In order to avoid silly mistakes
  As a math idiot
  I want to be told the sum of two number

Scenario: Add two numbers
  Given I have entered 50 into the calculator
  And I have entered 70 into the calculator
  When I press add
  Then the result should be 120 on the screen

Cucumber

  Given /I have entered (.*) into the calculator do
  	calculator = new Calculator();
  	calculator.push(n);
  end

expect

var expect = require("chai").expect
  , foo = "bar"
  , beverages = { tea: [ "chai", "matcha", "oolong" ] };

expect(foo).to.be.a("string");
expect(foo).to.equal("bar");
expect(foo).to.have.length(3);
expect(beverages).to.have.property("tea").with.length(3);

should

var should = require("chai").should()
  , foo = "bar"
  , beverages = { tea: [ "chai", "matcha", "oolong" ] };

foo.should.be.a("string");
foo.should.equal("bar");
foo.should.have.length(3);
beverages.should.have.property("tea").with.length(3);

Functional testing

UX/behavior verification

Unit tests just prove that your code doesn't work

Automation & Control

Metrics & Profiling

  • Execution time
  • Loading, rendering, painting
  • CPU & Memory
  • Google Chrome Metrics

Helps QA testers

Why not let QA guys concentrate on quality rather than routine?

Tools & examples

  • generators
  • frameworks
  • [assertion] libraries
  • plugins
  • stat tools
  • complex solutions + Grunt

Techniques

Dummies

Stubs

Mocks

Spies

Fixtures

Jasmine

What is it?..

Jasmine

Suites & specs

describe("A suite is just a function", function() {
  var a;

  it("and so is a spec", function() {
    a = true;

    expect(a).toBe(true);
  });
});
						

Jasmine

Matchers

expect(x).toEqual(y);
expect(x).toBe(y);
expect(x).toMatch(pattern);
expect(x).toBeDefined();
expect(x).toBeUndefined();
expect(x).toBeNull();
expect(x).toBeTruthy();
expect(x).toBeFalsy();
expect(x).toContain(y);
expect(x).toBeLessThan(y);
expect(x).toBeGreaterThan(y);
expect(function(){fn();}).toThrow(e);

Jasmine

Spies

Spies

Tracks

  • Functions calls
  • Arguments
  • Number of calls

Spies

Access

  • All calls of function
  • Every argument of every call

Spies

Can

  • Track and delegate
  • Substitute returning values
  • Call faked functions
  • Create mock objects

Jasmine

Any

describe("jasmine.any", function() {
  it("matches any value", function() {
    expect({}).toEqual(jasmine.any(Object));
    expect(12).toEqual(jasmine.any(Number));
  });
});

Jasmine

Clock

beforeEach(function() {
    timerCallback = jasmine.createSpy("timerCallback"); //create spy
    jasmine.Clock.useMock(); //use wrepper of system timer
});

it("causes a timeout to be called synchronously", function() {
    setTimeout(function() {
      timerCallback();
    }, 100);

    expect(timerCallback).not.toHaveBeenCalled();
    jasmine.Clock.tick(101); //make time to go
    expect(timerCallback).toHaveBeenCalled();
});

Jasmine

Async

It exists, but...

Jasmine

Reporter

describe("Jasmine", function() {
  it("makes testing JavaScript awesome!", function() {
    expect (yourCode).toBeLotsBetter();
  });
});

Mocha

['mɔkə]

Mocha

  • Supports TDD assertions and BDD should/expect
  • Reporting & CI integration
  • JavaScript API
  • Browser Test Runner

Mocha

describe('Array', function(){
  describe('#indexOf()', function(){
    it('should return -1 when the value is not present', function(){
      [1,2,3].indexOf(5).should.equal(-1);
      [1,2,3].indexOf(0).should.equal(-1);
    })
  })
})

Mocha

describe('User', function(){
  describe('#save()', function(){
    it('should save without error', function(done){
      var user = new User('Luna');
      user.save(function(err){
        if (err) throw err;
        done();
      });
    })
  })
})

Mocha

Hooks: before(), after(), beforeEach(), afterEach()

beforeEach(function(done){
db.clear(function(err){
  if (err) return done(err);
    db.save([tobi, loki, jane], done);
  });
})

Mocha

Mocha

Console reporter

Mocha

HTML reporter

Mocha

Nyan reporter

Chai

Chai

Assert, expect/should

chai.should();
foo.should.be.a('string');
foo.should.equal('bar');
foo.should.have.length(3);
tea.should.have.property('flavors')
  .with.length(3);

Question Time!

How would you test an RNG?

Chai

Plugins

here

CasperJS

PhantomJS

API

SlimerJS

SpookyJS

CasperJS

var casper = require('casper').create();

casper.start('http://domain.tld/page.html', function() {
    if (this.exists('h1.page-title')) {
        this.echo('the heading exists');
    }
});

casper.run();

Question Time!

What happens if not everyone on the team adopts TDD/BDD?

Code Coverage

Instrumentation

Instrumentation

Istanbul

  • Statement, branch, and function coverage
  • Test running tools
  • HTML & LCOV reporting
  • esprima-based

Testing + CI = ❤

  • fail builds
  • statistics & reporting
  • Github is integration paradise

Github is integration paradise

IRL, 100% coverage is a lie

  • legacy & untestable code
  • permissive tests
  • not applicable to functional testing

Mutation testing

  • Who tests tests?
  • Coverage is paramount! (it isn't)
  • Mutations: remove lines, alter operators, rename identifiers
  • The Future of Unit Testing

Question Time!

What do you do when you're offerred a project without TDD?

you run like hell

the end