JEST
Lucas Lira Gomes <@x8lucas8x>
Software Engineer
Passionate Hacker
ArchLinux Zealot
FOSS Enthusiast
I’m fond of doing
things that last
and all that jazz.
Lucas Lira Gomes
2
contact@x8lucas8x.com
linkedin.com/in/x8lucas8x
youtube.com/X80lucas08X
twitter.com/x8lucas8x
x8lucas8x.com
github.com/x8lucas8x
JEST
● Test Framework
● Built by
○ Facebook
● Alternative for
○ Jasmine
○ Mocha
○ ...
JEST
● DOM
○ JSDOM
○ Puppeteer
JEST
● Built in
○ Mocking
○ Coverage
○ Snapshots
○ Isolation
○ Watch mode
○ Failure awareness
API Overview
● it(name, fn, timeout)
○ test
test('can insert a thing', () => {
return globalDatabase.insert(
'thing', makeThing(), response => {
expect(response.success).toBeTruthy();
});
});
it('can insert another thing', () => {
return globalDatabase.insert(
'another thing', makeThing(), response => {
expect(response.success).toBeTruthy();
});
});
Scoping
● describe(name, fn)
○ describe(name, fn)
■ describe(name, fn)
● ...
test('city database has San Juan', () => {
expect(isCity('San Juan')).toBeTruthy();
});
describe('matching cities to foods', () => {
// Applies only to tests in this describe block
beforeEach(() => {
return initializeFoodDatabase();
});
test('Vienna <3 sausage', () => {
expect(isValidCityFoodPair('Vienna', 'Wiener
Schnitzel')).toBe(true);
});
test('San Juan <3 plantains', () => {
expect(isValidCityFoodPair('San Juan',
'Mofongo')).toBe(true);
});
});
Modifiers
● Apply to
○ test/it
○ describe
● Modifiers
○ each
○ only
○ skip
○ todo
describe.each([[1, 1, 2], [1, 2, 3], [2, 1, 3]])(
'.add(%i, %i)',
(a, b, expected) => {
test(`returns ${expected}`, () => {
expect(a + b).toBe(expected);
});
},
);
describe.only('my beverage', () => {
test('is delicious', () => {
expect(myBeverage.delicious).toBeTruthy();
});
});
test.skip('it is not snowing', () => {
expect(inchesOfSnow()).toBe(0);
});
test.todo('add should be associative');
Setup/Teardown
● Setup
○ beforeAll(fn)
○ beforeEach(fn)
● Teardown
○ afterAll(fn)
○ afterEach(fn)
● Supports async
beforeAll(() => {
return initializeCityDatabase();
});
afterAll(() => {
return clearCityDatabase();
});
beforeEach(() => {
return initializeCityDatabase();
});
afterEach(() => {
return clearCityDatabase();
});
test('city database has Vienna', () => {
expect(isCity('Vienna')).toBeTruthy();
});
Mocking - The JEST Object
● fn(fn)
● spyOn(object, methodName)
● mock(moduleName, factory, options)
● unmock(moduleName)
● resetAllMocks()
● restoreAllMocks()
● ...
Mocking - Module
● jest.mock(...) -> fn
○ moduleName
○ factory
○ options
● jest.unmock(...)
○ moduleName
// banana.js
module.exports = () => 'banana';
// __tests__/test.js
jest.mock('../banana');
// banana will be explicitly mocked.
const banana = require('../banana');
// will return 'undefined' because the function is
// auto-mocked.
banana();
// discard mock
jest.unmock('../banana');
Mocking - Object
● jest.spyOn(...) -> fn
○ object
○ methodName
● fn.mockRestore()
● fn.restoreAllMocks()
const video = require('./video');
test('plays video', () => {
const spy = jest.spyOn(video, 'play', 'get');
const isPlaying = video.play;
expect(spy).toHaveBeenCalled();
expect(isPlaying).toBe(true);
// restore former behaviour
spy.mockRestore();
});
Mocking - Timers
● jest
○ useFakeTimers()
○ useRealTimers()
○ runAllTimers()
○ ...
jest.useFakeTimers();
test('calls the callback after 1 second', () => {
const timerGame = require('../timerGame');
const callback = jest.fn();
timerGame(callback);
// At this point in time, the callback should not have
// been called yet
expect(callback).not.toBeCalled();
// Fast-forward until all timers have been executed
jest.runAllTimers();
// Now our callback should have been called!
expect(callback).toHaveBeenCalledTimes(1);
// Discard timer mocks
jest.useRealTimers();
});
Mocking - fn(fn)
● jest.fn(fn)
○ mockName(value) -> self
○ mockReset() -> self
○ mockRestore() -> self
○ mockImplementation(fn) -> self
○ mockImplementationOnce(fn) -> self
Mocking - Mock prop
● jest.fn(fn)
○ mock.calls
○ mock.results
○ mock.instances
const mockCallback = jest.fn(x => 42 + x);
mockCallback(0);
mockCallback(1);
// Calls
console.log(mockCallback.mock.calls)
// > [[0], [1]]
// Results (type in [return, throw, incomplete])
console.log(mockCallback.mock.results)
// > [{type: 'return', value: 42},
{type: 'return', value: 43}]
// Instances
console.log(ockCallback.mock.instances)
// > [this]
Mocking - Returns
● jest.fn(fn)
○ mockReturnThis() -> self
○ mockReturnValue(value) -> self
○ mockReturnValueOnce(value) -> self
Mocking - Promises
● jest.fn(fn) -> self
○ mockResolvedValue(value) -> self
○ mockResolvedValueOnce(value) -> self
○ mockRejectedValue(value) -> self
○ mockRejectedValueOnce(value) -> self
Manual Mocks
● Ideal for mocking
IO.
● Example:
○ jest.mock(‘./user’);
○ Directory structure
■ models
● __mocks__
○ user.js
● user.js
Snapshot Testing
● Compares HTML
○ Maintained as a file
○ Generated on first run
○ Needs to be updated
■ jest -u
// Updated test case with a Link to a different address
it('renders correctly', () => {
const tree = renderer.create(
<Link page="http://www.instagram.com">
Instagram
</Link>
).toJSON();
expect(tree).toMatchSnapshot();
});
...
exports[`renders correctly 1`] = `
<a
className="normal"
href="http://www.facebook.com"
onMouseEnter={[Function]}
onMouseLeave={[Function]}
>
Facebook
</a>
`;
Async
● Expect(...)
○ resolves
○ rejects
● Async/await
// The assertion for a promise must be returned.
it('works with promises', () => {
expect.assertions(1);
return user.getUserName(4).then(data =>
expect(data).toEqual('Mark')
);
});
// async/await can be used.
it('works with async/await', async () => {
expect.assertions(1);
const data = await user.getUserName(4);
expect(data).toEqual('Mark');
});
// async/await can also be used with `.resolves`.
it('works with async/await and resolves', async () => {
expect.assertions(1);
await expect(
user.getUserName(5)
).resolves.toEqual('Paul');
});
Good Practices
Watch mode
● --watch
● --watchAll
Don’t overuse snapshots
● Use snapshots for
○ Small
○ Self-contained
● Snap.js is not so
easy to follow
Codemod to migrate
● CLI
● Based on
○ JSCodeshift
● Supports
○ Chai
○ Jasmine
○ Mocha
○ ...
Constrain if slow
● --runInBand
○ Sequential
● --maxWorkers
○ 2 for Travis free plan
References
1. https://jestjs.io/
2. https://github.com/skovhus/jest-codemods
26
JEST
Lucas Lira Gomes <@x8lucas8x>

Jest

  • 1.
  • 2.
    Software Engineer Passionate Hacker ArchLinuxZealot FOSS Enthusiast I’m fond of doing things that last and all that jazz. Lucas Lira Gomes 2 contact@x8lucas8x.com linkedin.com/in/x8lucas8x youtube.com/X80lucas08X twitter.com/x8lucas8x x8lucas8x.com github.com/x8lucas8x
  • 3.
    JEST ● Test Framework ●Built by ○ Facebook ● Alternative for ○ Jasmine ○ Mocha ○ ...
  • 4.
  • 5.
    JEST ● Built in ○Mocking ○ Coverage ○ Snapshots ○ Isolation ○ Watch mode ○ Failure awareness
  • 6.
    API Overview ● it(name,fn, timeout) ○ test test('can insert a thing', () => { return globalDatabase.insert( 'thing', makeThing(), response => { expect(response.success).toBeTruthy(); }); }); it('can insert another thing', () => { return globalDatabase.insert( 'another thing', makeThing(), response => { expect(response.success).toBeTruthy(); }); });
  • 7.
    Scoping ● describe(name, fn) ○describe(name, fn) ■ describe(name, fn) ● ... test('city database has San Juan', () => { expect(isCity('San Juan')).toBeTruthy(); }); describe('matching cities to foods', () => { // Applies only to tests in this describe block beforeEach(() => { return initializeFoodDatabase(); }); test('Vienna <3 sausage', () => { expect(isValidCityFoodPair('Vienna', 'Wiener Schnitzel')).toBe(true); }); test('San Juan <3 plantains', () => { expect(isValidCityFoodPair('San Juan', 'Mofongo')).toBe(true); }); });
  • 8.
    Modifiers ● Apply to ○test/it ○ describe ● Modifiers ○ each ○ only ○ skip ○ todo describe.each([[1, 1, 2], [1, 2, 3], [2, 1, 3]])( '.add(%i, %i)', (a, b, expected) => { test(`returns ${expected}`, () => { expect(a + b).toBe(expected); }); }, ); describe.only('my beverage', () => { test('is delicious', () => { expect(myBeverage.delicious).toBeTruthy(); }); }); test.skip('it is not snowing', () => { expect(inchesOfSnow()).toBe(0); }); test.todo('add should be associative');
  • 9.
    Setup/Teardown ● Setup ○ beforeAll(fn) ○beforeEach(fn) ● Teardown ○ afterAll(fn) ○ afterEach(fn) ● Supports async beforeAll(() => { return initializeCityDatabase(); }); afterAll(() => { return clearCityDatabase(); }); beforeEach(() => { return initializeCityDatabase(); }); afterEach(() => { return clearCityDatabase(); }); test('city database has Vienna', () => { expect(isCity('Vienna')).toBeTruthy(); });
  • 10.
    Mocking - TheJEST Object ● fn(fn) ● spyOn(object, methodName) ● mock(moduleName, factory, options) ● unmock(moduleName) ● resetAllMocks() ● restoreAllMocks() ● ...
  • 11.
    Mocking - Module ●jest.mock(...) -> fn ○ moduleName ○ factory ○ options ● jest.unmock(...) ○ moduleName // banana.js module.exports = () => 'banana'; // __tests__/test.js jest.mock('../banana'); // banana will be explicitly mocked. const banana = require('../banana'); // will return 'undefined' because the function is // auto-mocked. banana(); // discard mock jest.unmock('../banana');
  • 12.
    Mocking - Object ●jest.spyOn(...) -> fn ○ object ○ methodName ● fn.mockRestore() ● fn.restoreAllMocks() const video = require('./video'); test('plays video', () => { const spy = jest.spyOn(video, 'play', 'get'); const isPlaying = video.play; expect(spy).toHaveBeenCalled(); expect(isPlaying).toBe(true); // restore former behaviour spy.mockRestore(); });
  • 13.
    Mocking - Timers ●jest ○ useFakeTimers() ○ useRealTimers() ○ runAllTimers() ○ ... jest.useFakeTimers(); test('calls the callback after 1 second', () => { const timerGame = require('../timerGame'); const callback = jest.fn(); timerGame(callback); // At this point in time, the callback should not have // been called yet expect(callback).not.toBeCalled(); // Fast-forward until all timers have been executed jest.runAllTimers(); // Now our callback should have been called! expect(callback).toHaveBeenCalledTimes(1); // Discard timer mocks jest.useRealTimers(); });
  • 14.
    Mocking - fn(fn) ●jest.fn(fn) ○ mockName(value) -> self ○ mockReset() -> self ○ mockRestore() -> self ○ mockImplementation(fn) -> self ○ mockImplementationOnce(fn) -> self
  • 15.
    Mocking - Mockprop ● jest.fn(fn) ○ mock.calls ○ mock.results ○ mock.instances const mockCallback = jest.fn(x => 42 + x); mockCallback(0); mockCallback(1); // Calls console.log(mockCallback.mock.calls) // > [[0], [1]] // Results (type in [return, throw, incomplete]) console.log(mockCallback.mock.results) // > [{type: 'return', value: 42}, {type: 'return', value: 43}] // Instances console.log(ockCallback.mock.instances) // > [this]
  • 16.
    Mocking - Returns ●jest.fn(fn) ○ mockReturnThis() -> self ○ mockReturnValue(value) -> self ○ mockReturnValueOnce(value) -> self
  • 17.
    Mocking - Promises ●jest.fn(fn) -> self ○ mockResolvedValue(value) -> self ○ mockResolvedValueOnce(value) -> self ○ mockRejectedValue(value) -> self ○ mockRejectedValueOnce(value) -> self
  • 18.
    Manual Mocks ● Idealfor mocking IO. ● Example: ○ jest.mock(‘./user’); ○ Directory structure ■ models ● __mocks__ ○ user.js ● user.js
  • 19.
    Snapshot Testing ● ComparesHTML ○ Maintained as a file ○ Generated on first run ○ Needs to be updated ■ jest -u // Updated test case with a Link to a different address it('renders correctly', () => { const tree = renderer.create( <Link page="http://www.instagram.com"> Instagram </Link> ).toJSON(); expect(tree).toMatchSnapshot(); }); ... exports[`renders correctly 1`] = ` <a className="normal" href="http://www.facebook.com" onMouseEnter={[Function]} onMouseLeave={[Function]} > Facebook </a> `;
  • 20.
    Async ● Expect(...) ○ resolves ○rejects ● Async/await // The assertion for a promise must be returned. it('works with promises', () => { expect.assertions(1); return user.getUserName(4).then(data => expect(data).toEqual('Mark') ); }); // async/await can be used. it('works with async/await', async () => { expect.assertions(1); const data = await user.getUserName(4); expect(data).toEqual('Mark'); }); // async/await can also be used with `.resolves`. it('works with async/await and resolves', async () => { expect.assertions(1); await expect( user.getUserName(5) ).resolves.toEqual('Paul'); });
  • 21.
  • 22.
  • 23.
    Don’t overuse snapshots ●Use snapshots for ○ Small ○ Self-contained ● Snap.js is not so easy to follow
  • 24.
    Codemod to migrate ●CLI ● Based on ○ JSCodeshift ● Supports ○ Chai ○ Jasmine ○ Mocha ○ ...
  • 25.
    Constrain if slow ●--runInBand ○ Sequential ● --maxWorkers ○ 2 for Travis free plan
  • 26.
  • 27.