Nextjs jest와 enzyme로 테스트하기

시작하기

Nextjs에 jest로 테스트를 추가해보도록 하겠습니다.
이전 포스트에서 i18n과 redux등을 추가 했었는데요.
테스트를 할때 useTranslation, useSelector, useDispatch가 에러가 나는 것도 해결해 보도록 하겠습니다.

설치하기

1
yarn add -D jest ts-jest enzyme enzyme-adapter-react-16 enzyme-to-json @types/enzyme-adapter-react-16 @types/enzyme @types/jest
  • jest: Jest는 단순성에 중점을 둔 JavaScript Testing Framework입니다.
  • ts-jest: Jest를 사용하여 TypeScript로 작성된 프로젝트를 테스트 할 수 있습니다.
  • enzyme: Enzyme은 React의 JavaScript 테스트 유틸리티로 React Components의 출력을보다 쉽게 ​​테스트 할 수 있습니다.
  • enzyme-adapter-react-16: 사용중인 React버전에 맞는 adapter를 설치해주어야합니다.
  • enzyme-to-json: 매우 쉽고 깨끗하고 간단한 스냅 샷 테스트를 작성할 수 있습니다.

이것으로 설치가 완료되었습니다.

설정하기

jest와 enzyme 설정을 해주도록 하겠습니다.

src/setupTests.ts
1
2
3
4
5
import { configure } from "enzyme";
import Adapter from "enzyme-adapter-react-16";

configure({ adapter: new Adapter() });
export default undefined;

처음으로 adapter설정을 해줍니다.

jest.config.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
module.exports = {
setupFilesAfterEnv: ["<rootDir>/src/setupTests.ts"],
testPathIgnorePatterns: ["<rootDir>/.next/", "<rootDir>/node_modules/"],
preset: "ts-jest",
transform: {
"^.+\\.tsx?$": "ts-jest"
},
moduleFileExtensions: ["ts", "tsx", "js", "jsx", "json", "node"],
snapshotSerializers: ["enzyme-to-json/serializer"],
globals: {
"ts-jest": {
tsConfig: "<rootDir>/tsconfig.jest.json"
}
}
};

snapshot, ts-jest와 enzyme setup파일을 불러오는 등 jest 설정을 합니다.

tsconfig.jest.json
1
2
3
4
5
6
{
"extends": "./tsconfig.json",
"compilerOptions": {
"jsx": "react"
}
}

jest에서는 컴파일 옵션을 react로 합니다.

package.json
1
2
3
4
5
6
7
8
9
10
{
//...
"scripts": {
//...
"test": "jest",
"test:watch": "jest --watchAll"
//...
}
//...
}

테스트 스크립트를 추가해줍니다.

적용하기

테스트 설정을 적용해보겠습니다.

pages/index.spec.tsx
1
2
3
4
5
6
7
8
9
10
11
12
13
import React from "react";
import { mount } from "enzyme";
import Index from "./index";
import { includeDefaultNamespaces } from "../src/i18n";

describe("Index page", () => {
it("match snapshot", () => {
const wrapper = mount(
<Index namespacesRequired={includeDefaultNamespaces(["Index"])} />
);
expect(wrapper).toMatchSnapshot();
});
});

테스트를 해보도록 합니다.
react-i18next warning
테스트를 했는데 다음과 같은 경고가 에디터 콘솔에 나옵니다. 해결을 해보도록 하겠습니다.

src/__mocks__/mockReactI18next.ts파일을 생성을하고 설정을 해줍니다.

src/__mocks__/mockReactI18next.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
let selectedLanguage = "en";
const useMock = {
t: (k: string) => k,
i18n: {
language: selectedLanguage,
changeLanguage: (lang: string) => (selectedLanguage = lang)
}
};

const reactI18next = jest.mock("react-i18next", () => ({
withTranslation: jest
.fn()
.mockImplementation(() => (Component: React.FC | React.ComponentClass) => {
const ComponentCopy = { ...Component };
ComponentCopy.defaultProps = {
...Component.defaultProps,
t: (k: string) => k
};
return ComponentCopy;
}),
useTranslation: () => useMock
}));

export default reactI18next;
jest.config.js
1
2
3
4
5
6
7
module.exports = {
//...
moduleNameMapper: {
"react-i18next": "<rootDir>/src/__mocks__/mockReactI18next.ts"
}
//...
};

설정한 파일을 맵핑해줍니다.
그리고 다시 yarn run test로 테스트를 실행해줍니다.

react-i18next warning 해결

react redux 테스트 설정

Redux를 사용하지 않는다면 넘어가도 됩니다.
Redux를 사용한다면 useSelector와 useDispatch가 에러가 발생하는데 수정해보도록 하겠습니다.

1
yarn add redux-mock-store && yarn run add -D @types/redux-mock-store
src/__mocks__/mockStore
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import createMockStore, { MockStore } from "redux-mock-store";
import * as ReactRedux from "react-redux";
// Redux의 store를 불러옵니다.
import store from "../store";

const mockStore = createMockStore([]);
const makeMockStore = (
state = store.getState() || {}
): MockStore<unknown> & { dispatch: {} } => mockStore({ ...state });

export const mockUseReactRedux = () => {
const spyUseSelector = jest.spyOn(ReactRedux, "useSelector");
spyUseSelector.mockImplementation(() => store.getState());
const spyDispatch = jest.spyOn(ReactRedux, "useDispatch");
spyDispatch.mockImplementation(() => store.dispatch);
};

export default makeMockStore;

pages에 redux를 사용한다고 가정하고 테스트코드를 작성해보겠습니다.

pages/index.spec.tsx
1
2
3
4
5
6
7
8
9
10
11
12
13
14
import React from "react";
import { mount } from "enzyme";
import Index from "./index";
import { includeDefaultNamespaces } from "../src/i18n";
import { mockUseReactRedux } from "../__mocks__/mockStore";

describe("Index page", () => {
beforeAll(() => {
mockUseReactRedux();
});
/*
Test code...
*/
});

이후 테스트를 시도해본다면 에러는 나오지 않습니다.
이 방법을 사용하거나 다른 한가지 방법을 사용하면 됩니다.

pages/index.spec.tsx
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import React from "react";
import { mount } from "enzyme";
import Index from "./index";
import { Provider } from "react-redux";
import { includeDefaultNamespaces } from "../src/i18n";
import { makeMockStore } from "../__mocks__/mockStore";

const store = makeMockStore();

describe("Index page", () => {
it("react-redux test", () => {
const wrapper = mount(
<Provider store={store}>
<Index namespacesRequired={includeDefaultNamespaces(["Index"])} />
<Provider/>
);
/*
Test code...
*/
})
});

styled-components 테스트

jest-styled-components를 설치해줍니다.

1
yarn add -D jest-styled-components
src/setupTests.ts
1
2
3
4
5
6
import { configure } from "enzyme";
import Adapter from "enzyme-adapter-react-16";
import "jest-styled-components";

configure({ adapter: new Adapter() });
export default undefined;

jest-styled-components를 설정합니다.

이것으로 i18next와 react-redux등을 jest와 enzyme를 통해 next에서 테스트하였습니다.
i18next와 react-redxu를 테스트 설정하는 과정이 복잡하다는 것을 알 수 있었습니다.
enzyme는 기존 class 형식에는 지원이 잘되어있습니다.
하지만 16.8 버전 이상에 훅스가 나오면서 함수 형식으로 컴포넌트를 작성하고 훅스를 사용하면서 에러나 경고를 많이 접하게 됩니다. 다음 업데이트가 있을때까지는 이렇게 사용해야 할 것 같습니다.

Share