はじめに
- この記事はNext.js使っていて、これからUnitテスト書こうと思っている方向けの記事です
- 大まかな流れ
- 他のNext.jsプロジェクトでどんなテストライブラリ使っているか調べる
- テスト環境を整える
- 簡単なテストを書いてみる
- How to
- まとめ
Next.js + Typescript構成でよく使われているテストライブラリ
- jestはどこのプロジェクトでも使っていそう
- Reactのコンポーネントのテストは Enzyme vs React Testing Library という感じ
- EnzymeとReact Testing Libraryの比較は以下の記事がとても参考になり読ませていただきました
- 今回はUnitテストが書きやすそうな
Enzyme
でやっていこうと思います
環境構築や準備
各バージョンや環境
- nextjs: 10.0.1
- enzyme: 3.11.0
- enzyme-adapter-react-16: 1.15.5
- jest: 26.6.3
- react-test-renderer: 17.0.1
もろもろ必要なパッケージをインストール
$ yarn add -D jest ts-jest react-test-renderer enzyme enzyme-adapter-react-16 enzyme-to-json @types/react-test-renderer @types/jest @types/enzyme-adapter-react-16
react-test-rendererとは?
- React コンポーネントをピュアな JavaScript オブジェクトにレンダーすることができる React レンダラを提供
- スナップショットテストで使用
- 出力を走査して特定のノードを検索し、それらに対してアサーションを行うことも可能
1. 簡単なテストを動かせるまでの環境構築
1-1. テスト用のディレクトリ作成
プロジェクトの直下に __tests__
ディレクトリを作成し、テストや設定はこちらに実装していきたいと思います。
この中に setupTests.ts
と tsconfig.jest.json
を以下内容で作成します。
import Enzyme from 'enzyme';
import Adapter from 'enzyme-adapter-react-16';
Enzyme.configure({ adapter: new Adapter() });
process.env = {
...process.env,
__NEXT_IMAGE_OPTS: {
deviceSizes: [320, 420, 768, 1024, 1200],
imageSizes: [],
domains: ['images.example.com'],
path: '/_next/image',
loader: 'default',
} as any,
};
__NEXT_IMAGE_OPTS
に関しては後述のバッドノウハウでも出てきますが、next/image
を使っている箇所をテストする際に必要です。
{
"extends": "../tsconfig.json",
"compilerOptions": {
"jsx": "react"
}
}
次にテスト対象のComponentで css
ファイルやリソースファイルをimportしていた時にエラーになってしまうので、
モック用のファイルを作成してやります。(これもバッドノウハウで後述)
__tests__/mocks/fileMock.js
と __tests__/mocks/styleMock.js
を以下内容で作成します。
module.exports = 'test-file-stub';
module.exports = {};
両方ともモックなので読み込まれても特に何もしません。
1-2. jest.config.js
の作成
上記を踏まえた jest.config.js
を以下内容で作成します。
module.exports = {
preset: 'ts-jest',
testEnvironment: 'node',
roots: [
'<rootDir>/__tests__'
],
setupFilesAfterEnv: ['<rootDir>/__tests__/setupTests.ts'],
testPathIgnorePatterns: [
'<rootDir>/__tests__/setupTests.ts',
'<rootDir>/__tests__/tsconfig.jest.json',
'<rootDir>/__tests__/mocks/'
],
snapshotSerializers: ['enzyme-to-json/serializer'],
transform: {
'^.+\\.(ts|tsx)$': 'ts-jest'
},
moduleFileExtensions: [
'ts',
'tsx',
'js',
'jsx',
'json',
'node'
],
// https://github.com/zeit/next.js/issues/8663#issue-490553899
globals: {
// we must specify a custom tsconfig for tests because we need the typescript transform
// to transform jsx into js rather than leaving it jsx such as the next build requires. you
// can see this setting in tsconfig.jest.json -> "jsx": "react"
'ts-jest': {
'tsconfig': '<rootDir>/__tests__/tsconfig.jest.json'
}
},
collectCoverage: false,
collectCoverageFrom: ["src/**/*"],
coverageDirectory: "./coverage/",
moduleNameMapper:{
"\\.(css|less|sass|scss)$": "<rootDir>/__tests__/mocks/styleMock.js",
"\\.(gif|ttf|eot|svg)$": "<rootDir>/__tests__/mocks/fileMock.js"
}
};
簡単なテストを書いてみる
1. テスト対象のコンポーネント
テスト対象として以下の Avator
コンポーネントでテストを書いてみます。
Avator
コンポーネント自体はとてもシンプルで指定された画像を指定されたサイズの円内に表示するコンポーネントです。
import React from 'react';
export type AvatorProps = {
imageUrl: string;
width: number;
height: number;
};
const Avator: React.FC<AvatorProps> = ({ imageUrl, width, height }) => {
return (
<div>
<img
src={imageUrl}
style={{
width: width,
height: height,
borderRadius: '50%',
}}
/>
</div>
);
};
export default Avator;
2. テストコードを書いてみる
Avator
コンポーネントをテストするコードを書いてみたいと思います。
テスト内容としては Avator
コンポーネント内に <img>
要素があって、そこに指定した画像URLとサイズがちゃんと設定されているかというものです。
import { shallow } from 'enzyme';
import * as React from 'react';
import Avator from 'xxxx/Avator';
describe('Avator', () => {
it('必要な要素に指定された値が設定されているはず', () => {
const wrapper = shallow(
<Avator
imageUrl={'https://randomuser.me/api/portraits/women/26.jpg'}
width={50}
height={50}
/>
);
expect(wrapper.find('img')).toHaveLength(1);
const src = wrapper.find('img').prop('src');
expect(src).toEqual('https://randomuser.me/api/portraits/women/26.jpg');
const style = wrapper.find('img').prop('style');
expect(style.width).toEqual(50);
expect(style.height).toEqual(50);
});
});
Enzymeのshallow render APIを使って Avator
コンポーネント をshallow renderしたものを wrapper
として受け取っています。
テストはこの wrapper
を使って要素が存在するか、要素に指定した値が設定されているかをテストしています。
パッと見そこまで難しくはなく書きやすい感じですね ✨
Enzyme How to
筆者がテスト書いていて「これどうやってテストするの?」と思った際のメモになります。
1. shallowでの src
属性を取得したい時
const wrapper = shallow(
<App />
);
const src = wrapper.find('img').prop('src');
find
と props
を使う。
2. window.confirm を含めてテストしたい場合
declare const global;
describe('...', () => {
beforeEach(() => {
global.window = {};
});
describe('confirm', () => {
it('XXのメッセージが表示されるはず', () => {
const mockCallWindow = jest.fn(() => true);
global.window.confirm = mockCallWindow;
const wrapper = shallow(
<ConfirmDialog/>
);
wrapper.find('.show-button').simulate('click');
expect(mockCallWindow).toHaveBeenCalledWith("XX");
});
});
javascript - Simulate clicking "Ok" or "Cancel" in a confirmation window using enzyme - Stack Overflowjavascript - Simulate a button click in Jest - Stack Overflow
筆者の環境では上記でやったら上手く行きました。
3. useStateやuseEffectを使っているコンポーネントのテスト
React Hooks Jest + enzyme + act で useEffect を含むコンポーネントのテストする - かもメモ
バッドノウハウ
1.TypeError: Cannot destructure property
deviceSizesof 'undefined' or 'null'.
が発生する!
エラーの詳細
TypeError: Cannot destructure property `deviceSizes` of 'undefined' or 'null'.
at Object.<anonymous> (node_modules/next/client/image.tsx:62:9)
at Object.<anonymous> (node_modules/next/image.js:1:107)
原因
next/image
を使用している箇所で deviceSizes
が設定されていない
解決方法
setupTests.ts
に以下を追加する
process.env = {
...process.env,
__NEXT_IMAGE_OPTS: {
deviceSizes: [320, 420, 768, 1024, 1200],
imageSizes: [],
domains: ['images.example.com'],
path: '/_next/image',
loader: 'default',
} as any,
};
参考リンク
- Jest next/image: Cannot destructure property
deviceSizes
of 'undefined' or 'null' · Discussion #18373 · vercel/next.js - https://github.com/search?l=TypeScript&q=__NEXT_IMAGE_OPTS&type=Code
2. テストで sass
読み込みでエラーになる
Setting Up a Next.js Project With TypeScript, Sass, and Jest | by Aristos Markogiannakis | Better Programming | Medium
上記リンクの通りmock用のscssファイルとリソースファイルを用意してテスト時はそれを読み込むようにした。
3. window
をテストの中でも使えるようにする
declare const global;
describe('', () => {
beforeEach(() => {
global.window = {};
});
...
global.window = {};
global.window.confirm = jest.fn(() => true);
);
まとめ
EnzymeとJestでUnitテストはとても書きやすく、すんなりテストする事ができた印象でした。
ただ、Next.jsの場合、固有のエラーがちらほら起きたので、そこだけは地道に突破していくしか無さそうかなという感じです。