Unit testing

with javascript

Tester, c'est douter ?

HELL NO !

Quality is never an accident; it is always the result of intelligent effort.

John Ruskin

Types de tests

  • Test unitaire : Tester des morceaux de code isolés (fonctions, ...).
  • Test d’intégration : Tester les interactions entre les modules unitaires.
  • Test fonctionnel : Tester les comportements attendus dans les spécifications via des scénarios. Test via l’application.
  • Mais aussi : test de validation, test de performance, test de charge, stress test, …

Avantages

  • Facilite les changements,
  • Evite les régressions et permet de rencontrer des problèmes plus tôt,
  • Permet de documenter le code en livrant des exemples d’usages,
  • Force à avoir une approche modulaire (fonctions courtes, …).

Un bon test unitaire ?!

  • Peut être lancé tout seul et dans n’importe quel ordre,
  • Isolation : s’adresse à un fichier unique (pas besoin d’importer ses dépendances : mocks et stubs),
  • N’utilise pas d’accès disque ni base de donnée (accès mémoire uniquement).
  • Retourne impérativement toujours le même résultat,
  • Est rapide, lisible et facile à maintenir.

 

Un bon test unitaire ?! (2)

Software testing proves the existing of bugs not their absence.

Les outils

Runners

Frameworks

Tools

TDD vs BDD

TDD (Test Driven Dev)

Ecrire les tests d’abord => TEST FAIL

Ecrire le code jusqu'à ce que ça passe => TEST OK

 

⇒ Permet de ne pas se focaliser sur le besoin,

⇒ Meilleur moyen de ne pas oublier.

 

Syntaxe : suite(), test(), ...

TDD (Test Driven Dev)

BDD (Behavior Driven Dev)

TDD avec une syntaxe plus lisible.

 

You should not test implementation, but instead behavior.

 

BDD tests should be more focused on the features, not the actual results

 

Syntaxe : describe(), it(), ...

TDD

suite('Counter', function() {
  test('tick increases count to 1', function() {
    var counter = new Counter();
    counter.tick();
    assert.equal(counter.count, 1);
  });
});
describe('Counter', function() {
  it('should increase count by 1 after calling tick', function() {
    var counter = new Counter();
    var expectedCount = counter.count + 1;
    counter.tick();
    assert.equal(counter.count, expectedCount);
  });
});

BDD

TDD vs BDD

Mocha

npm install -g mocha

Test framework

Hooks

describe('hooks', function() {

  before(function() {
    // runs before all tests in this block
  });

  after(function() {
    // runs after all tests in this block
  });

  beforeEach(function() {
    // runs before each test in this block
  });

  afterEach(function() {
    // runs after each test in this block
  });

  // test cases
  it('should do something', function() {
    // ...
  });

});

Asynchronous

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(); // Call the done callback
      });
    });
  });
});

Usage

mocha // Launch the tests
mocha --watch // Live watch
mocha --debug // Use the Node's debugger

With the global mocha command

var mocha = require('mocha');

gulp.task('test', function () {
  return gulp.src('test/**/*.spec.js', { read: false })
    .pipe(mocha({ reporter: 'nyan' }));
});

With a Gulp task

gulp test

gulpfile.js

Reporters

mocha --reporter="spec"

nyan

Landing Strip

Exercise (step1)

https://gitlab.fullsix.com/dfo-internal/mocha-training

Step 1: Branche "master"
Instructions dans le Readme

Chai

npm install chai

Assertion library

Chai is a BDD / TDD assertion library for node
chai.should();

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

Interfaces

var expect = chai.expect;

expect(foo).to.be.a('string');
expect(foo).to.equal('bar');
expect(foo).to.have.length(3);
expect(tea).to.have.property('flavors').with.length(3);
var assert = chai.assert;

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

should

expect

assert

should chaining

 

  • to
  • be
  • been
  • is
  • that
  • which
  • and
  • has
  • have
  • with
  • at
  • of
  • same
// .include
expect([1,2,3]).to.include(2);
// .contain
expect('foobar').to.contain('foo');
// .ok (truthy)
expect('everthing').to.be.ok;
// .true
expect(true).to.be.true;
// .false
expect(false).to.be.false;
// .any
expect(foo).to.have.any.keys('bar', 'baz');
// .all
expect(foo).to.have.all.keys('bar', 'baz');
// .exist (!null && !undefined)
expect(foo).to.exist;
// .empty
expect([]).to.be.empty; expect('').to.be.empty; expect({}).to.be.empty;
// .equal
expect('hello').to.equal('hello');
// throw an error
var fn = function () { throw err; };
expect(fn).to.throw(ReferenceError);

Exercise (step2)

https://gitlab.fullsix.com/dfo-internal/mocha-training

Step 2: Checkout branche "step2"
Instructions dans le Readme

Sinon

npm install sinon

Test spies, stubs and mocks

Standalone test spies, stubs and mocks for JavaScript.

Spies

// Test
it('should call callAPI', function() {
  const url = 'http//xxx.xx/videos/count';

  sinon.spy(Media, 'callAPI');
  Media.countVideos();

  expect(Media.callAPI.calledOnce).to.be.true;
  expect(Media.callAPI.withArgs(url).calledOnce).to.be.true;
  Media.callAPI.restore();
});
// Source
class Media {

  static callAPI(url) {
    // ...
  }

  static countVideos() {
    return Media.callAPI('http//xxx.xx/videos/count');
  }

}

How to spy objects and methods !

// Test
it('should count the videos', function(){
  var stub = sinon.stub(Media, 'callAPI');
  stub.returns(100);
  expect(Media.countVideos()).to.equal(100);
  Media.callAPI.restore()
});

Stubs

Change the behavior of a function

// Source
class Media {

  static callAPI(url) {
    // ...
  }

  static countVideos() {
    return Media.callAPI('http//xxx.xx/videos/count');
  }

}
it('should increment stored value by one', function() {

  var storeMock = sinon.mock(store);

  storeMock.expects('get').withArgs('data').returns(0);
  storeMock.expects('set').once().withArgs('data', 1);

  incrementStoredData();

  storeMock.restore();
  storeMock.verify();
});

Mocks

Sinon’s mocks can be used to replace whole objects and alter their behavior similar to stubbing functions.

Exercise (step3)

https://github.com/juherpin/mocha-exercises

Step 3: Checkout branche "step3"
Instructions dans le Readme

Exercise (solution)

https://gitlab.fullsix.com/dfo-internal/mocha-training

Solution: Checkout branche "end"

Exercise (Gitlab CI)

https://gitlab.fullsix.com/dfo-internal/mocha-training

Solution: Checkout branche "ci"

Frontend unit testing

By Julien Herpin

Frontend unit testing

  • 1,095