Below are some examples / patterns / recipes / approaches you may find useful when developing.
Following Test Driven Development you will write your tests, and develop your code until your tests are green.
The watch mechanism incorporated in to Jest is very powerful
in this scenario. It can be activated with yarn run test --watch
It is possible to debug your Jest tests with ease. First add a debugger;
statement as appropriate:
import * as metricsService from "./metrics-service";
export default function ({ eventType }) {
debugger;
metricsService.send({
event: `my_application_namespace.${eventType}`,
time: Date.now(),
});
}
The command to run your tests is slightly different:
# Run all tests. `--runInBand` stops Jest from running in parallel.
$ node --inspect-brk node_modules/.bin/jest --runInBand
# Running particular file
$ node --inspect-brk node_modules/.bin/jest --runInBand ./src/metrics
We can now see that the Jest process is paused until a debugger attaches:
There are various debuggers available, however for this workshop we will use Chrome's. It is accessible via:
chrome://inspect/#devices
As a full example:
At the time of writing there is also support for:
Although test descriptions and structure can be arbitrary, it can be useful to follow similar conventions. Although aimed at Ruby, you can follow some of the approaches recommended by http://www.betterspecs.org/
For instance, you may like the convention for writing descriptions such as:
#
to describe functions you wish to test, #add
::
to describe class methodsYou may also find it easier to have one single expectation per test. This means if you are applying TDD, you can gradually make your tests pass, rather than attempt to make one test pass - which actually contains multiple assertions.
This can be combined with using it.only
to focus on one particular assertion at a time.
For example:
import * as service from "../";
describe("movies-api", function () {
describe("#fetchMovies", function () {
describe("when there are no movies available", function () {
let testContext;
beforeEach(async function () {
testContext = {};
const fetchMock = jest.spyOn(global, "fetch");
fetchMock.mockReturnValueOnce(
Promise.resolve({
json: () => Promise.resolve({ movies: [] }),
})
);
testContext.result = await service.fetchMovies(); });
it("returns the data", function () { expect(testContext.result).toEqual({ movies: [] }); });
it("fetches from the correct endpoint", function () {
expect(fetch).toHaveBeenCalledWith(
"http://www.alanfoster.me/movies.json"
);
});
});
});
});
You can extend Jest's default matchers. There are various open source libraries available, for instance:
Or you can add your own custom matchers:
expect.extend({
toBePotato(received) {
if (received === "potato") {
return {
message: () => `expected ${received} to not be potato`,
pass: true,
};
} else {
return {
message: () => `expected ${received} to be potato`,
pass: false,
};
}
},
});
it("works successfully", function () {
expect("potato").toBePotato();
expect("sausage roll").not.toBePotato();
});
There is also support for colored diffing etc, full details.
If your code is hard to test, perhaps there is an issue with your code. A common testing smell is a test which heavily relies on various complex mocks, or one which requires many imports.