JavaScript Testing
For more details checkout Testing
Unit Testing Libraries
Test runners:
Mocha
Mocha is a test framework for Node.js programs, featuring:
- Browser support
- Asynchronous testing
- Test coverage reports
- Use of any assertion library
- Really good for Node.js projects
- No Test double library included (use Sinon)
- No assertions library included (use Chai)
Example:
var expect = require("chai").expect;
// test suite
describe("Mocha", function () {
// test spec (unit test)
it("should run our tests using Mocha", function () {
expect(true).to.be.ok;
});
});
TODO: Check mocha's execution context being reset after each test case execution?
Chai
Chai is an expectation or assertion library
expect
is used to run BDD style test casesjavascriptvar expect = require("chai").expect; expect(true).to.be.true;
The above code will not provide any output if the expectation is met
Jest
Jest is a JavaScript unit testing framework, used by Facebook to test services and React applications
It is a test runner that lets us access the DOM via js-dom
js-dom is a JavaScript implementation of various web standards, for use with Node.js
js-dom is only an approximation of how the browser works, it is often good enough for testing React components
Test runner that:
- Finds tests
- Runs tests
- Determines whether tests pass or fail
It has an assertion library and mocking library
Jest looks for filenames ending with
.test.js
Setup:
Install it using any package manager:
bashnpm install --save-dev jest
Generate a basic configuration file:
bashjest --init
Write test cases
Run test cases
Example:
// sum.js
function sum(a, b) {
return a + b;
}
module.exports = sum;
// sum.test.js
const sum = require("./sum");
test("adds 1 + 2 to equal 3", () => {
expect(sum(1, 2)).toBe(3);
});
Tests
Global
test
orit
method has 2 required and 1 optional argument: creates a test closurename
: The name of your testfn
: The function for your testtimeout
: The timeout for an async function test
test("adds 1 + 2 to equal 3", () => {
expect(sum(1, 2)).toBe(3);
});
// or
it("adds 1 + 2 to equal 3", () => {
expect(sum(1, 2)).toBe(3);
});
Test fails if error is thrown when running function
- Assertions throw errors when expectation fails
No error --> tests pass
- Empty test passes!
Run only this test:
javascripttest.only("adds 1 + 2 to equal 3", () => { expect(sum(1, 2)).toBe(3); }); fit("adds 1 + 2 to equal 3", () => { expect(sum(1, 2)).toBe(3); });
Skip this test:
javascripttest.skip("adds 1 + 2 to equal 3", () => { expect(sum(1, 2)).toBe(3); }); xit("adds 1 + 2 to equal 3", () => { expect(sum(1, 2)).toBe(3); });
Suite
Suite is a specific group of related tests
- In jest each file is test suite
Tests can be grouped together using a describe
block
- Each test file can have any number of
describe
blocks
// describe(name: 'group name', fn: 'function that contains tests')
describe("matching cities to foods", () => {
test("Vienna <3 veal", () => {
expect(isValidCityFoodPair("Vienna", "Wiener Schnitzel")).toBe(true);
});
test("San Juan <3 plantains", () => {
expect(isValidCityFoodPair("San Juan", "Mofongo")).toBe(true);
});
});
Matchers
"Matchers" let you test values in different ways
expect()
returns an "expectation" object, we can call matchers on them
Commonly used matchers:
Truthiness:
toBeNull
matches onlynull
toBeUndefined
matches onlyundefined
toBeDefined
is the opposite oftoBeUndefined
toBeTruthy
matches anything that anif
statement treats astrue
toBeFalsy
matches anything that anif
statement treats asfalse
Numbers:
toBeGreaterThan()
toBeGreaterThanOrEqual()
toBeLessThan()
toBeLessThanOrEqual()
toBe
andtoEqual
are equivalent for numbers- For floating point equality, use
toBeCloseTo
instead oftoEqual
, because you don't want a test to depend on a tiny rounding error
Strings:
toMatch
: check strings against regular expressions
javascripttest("there is no I in team", () => { expect("team").not.toMatch(/I/); }); test('but there is a "stop" in Christoph', () => { expect("Christoph").toMatch(/stop/); });
Arrays and iterables:
toContain
: array or iterable contains a particular item
Exceptions:
toThrow
: test whether a particular function throws an error when it's called
javascriptfunction compileAndroidCode() { throw new Error("you are using the wrong JDK"); } test("compiling android goes as expected", () => { expect(() => compileAndroidCode()).toThrow(); expect(() => compileAndroidCode()).toThrow(Error); // You can also use the exact error message or a regexp expect(() => compileAndroidCode()).toThrow("you are using the wrong JDK"); expect(() => compileAndroidCode()).toThrow(/JDK/); });
Mocking
const add = jest.fn(() => 3);
test("adds 1 + 2 to equal 3", () => {
expect(add(1, 2)).toBe(3);
expect(add(9, 6)).toBe(3);
});
// test passed
test("adds 1 + 2 to equal 3", () => {
expect(add(1, 2)).toBe(3);
expect(add).toHaveBeenCalledTimes(1);
expect(add).toHaveBeenCalledWith(1, 2);
});
Manually mocking
fetch
:javascript// src/utils/currency.test.js global.fetch = jest.fn(() => Promise.resolve({ json: () => Promise.resolve({ rates: { CAD: 1.42 } }), }), ); beforeEach(() => { fetch.mockClear(); }); it("finds exchange", async () => { const rate = await convert("USD", "CAD"); expect(rate).toEqual(1.42); expect(fetch).toHaveBeenCalledTimes(1); }); // Mocks with failure it("returns null when exception", async () => { fetch.mockImplementationOnce(() => Promise.reject("API is down")); const rate = await convert("USD", "CAD"); expect(rate).toEqual(null); expect(fetch).toHaveBeenCalledWith( "https://api.exchangeratesapi.io/latest?base=USD", ); });
Faking API calls made using
fetch
:javascript// src/utils/currency.js async function convert(base, destination) { try { const result = await fetch( `https://api.exchangeratesapi.io/latest?base=${base}`, ); const data = await result.json(); return data.rates[destination]; } catch (e) { return null; } } export { convert };