Building a Blog - Part 2: Testing
16 May 2020Testing is important in software development, as it gives us confidence that our solution is working as expected, as well as, preventing bugs from appearing.
Jest
Introducing Jest. Jest is created by facebook, who also created React, which Gatsby is based upon. Therefore it made sense to also use it as my testing library of choice.
Configuration
Before we can start using Jest, we have to sort out some configuration. As of writing, this is my current configuration.
I will not go through it, but it's there as a reminder to me.
// jest.config.js
module.exports = {
transform: {
'^.+\\.jsx?$': `<rootDir>/test/jest-preprocess.js`,
},
moduleNameMapper: {
'@core(.*)': `<rootDir>src/core$1`,
'@post(.*)': `<rootDir>src/modules/post$1`,
'@shared(.*)': `<rootDir>src/shared$1`,
'.+\\.(css|styl|less|sass|scss)$': `identity-obj-proxy`,
'.+\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$': `<rootDir>/test/__mocks__/file-mock.js`,
},
testPathIgnorePatterns: [
`node_modules`,
`\\.cache`,
`<rootDir>.*/public`,
`__fixtures__`,
],
transformIgnorePatterns: [`node_modules/(?!(gatsby)/)`],
globals: {
__PATH_PREFIX__: ``,
},
setupFiles: [`<rootDir>/test/loadershim.js`],
snapshotSerializers: ['jest-emotion'],
};As well as that, we need to setup mocks,
which I put in test/__mocks__.
// file-mock.js
module.exports = 'test-file-stub';// gatsby.js
const React = require('react');
const gatsby = jest.requireActual('gatsby');
module.exports = {
...gatsby,
graphql: jest.fn(),
Link: jest.fn().mockImplementation(
// these props are invalid for an `a` tag
({
activeClassName,
activeStyle,
getProps,
innerRef,
partiallyActive,
ref,
replace,
to,
...rest
}) =>
React.createElement('a', {
...rest,
href: to,
})
),
StaticQuery: jest.fn(),
useStaticQuery: jest.fn(),
};// react-helmet.js
const React = require('react');
const reactHelmet = jest.requireActual('react-helmet');
module.exports = {
...reactHelmet,
Helmet: jest.fn(),
};In order to get to get it working with Gatsby, we have to use a preprocessor.
// jest-preprocess.js
const babelOptions = {
presets: ['babel-preset-gatsby'],
};
module.exports = require('babel-jest').createTransformer(babelOptions);Testing
Jest's syntax is very similar to other Javascript testing frameworks. However, there is a feature which I really like. Snapshot Testing.
Your test will produce a snapshot, which you can commit into source control. Any future changes which modify the snapshot, which can be then reviewed.
// header.test.js
import React from 'react';
import renderer from 'react-test-renderer';
import Header from '../header';
const siteTitle = 'Blog';
describe('Header', () => {
it('renders correctly', () => {
const tree = renderer.create(<Header siteTitle={siteTitle} />).toJSON();
expect(tree).toMatchSnapshot();
});
});Now if you run
jestor if configured in package.json.
npm testThis produced the following snapshot, which I will commit.
// __snapshots__/header.test.js.snap
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Header renders correctly 1`] = `
.emotion-1 {
background-color: #6b46c1;
display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
-webkit-align-items: center;
-webkit-box-align: center;
-ms-flex-align: center;
align-items: center;
-webkit-box-pack: center;
-webkit-justify-content: center;
-ms-flex-pack: center;
justify-content: center;
-webkit-flex-wrap: wrap;
-ms-flex-wrap: wrap;
flex-wrap: wrap;
}
.emotion-0 {
color: #fff;
width: 100%;
padding-left: 3rem;
padding-right: 3rem;
padding-top: 0.5rem;
padding-bottom: 0.5rem;
}
@media (min-width:768px) {
.emotion-0 {
max-width: 640px;
padding-left: 0.5rem;
padding-right: 0.5rem;
}
}
@media (min-width:1024px) {
.emotion-0 {
max-width: 768px;
}
}
<header
className="emotion-1"
>
<div
className="emotion-0"
>
<h1>
<a
href="/"
>
Blog
</a>
</h1>
</div>
</header>
`;If we make a change to code and we want to update the snapshot,
the -u flag needs to be passed in.