Building a Blog - Part 2: Testing

16 May 2020

Testing 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

jest

or if configured in package.json.

npm test

This 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.

© Copyright 2020, dctcheng. All Rights Reserved