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
expectis 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 jestGenerate a basic configuration file:
bashjest --initWrite 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
testoritmethod 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
describeblocks
// 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:
toBeNullmatches onlynulltoBeUndefinedmatches onlyundefinedtoBeDefinedis the opposite oftoBeUndefinedtoBeTruthymatches anything that anifstatement treats astruetoBeFalsymatches anything that anifstatement treats asfalse
Numbers:
toBeGreaterThan()toBeGreaterThanOrEqual()toBeLessThan()toBeLessThanOrEqual()toBeandtoEqualare equivalent for numbers- For floating point equality, use
toBeCloseToinstead 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 };
