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