【React】JestでSnapshotテストをしたいならCSS ModulesをやめてCSS-in-JSに切り替えよう


ざっくり要約

  • Reactのスタイリングの種類はstyle属性,CSS Modules,CSS-in-JSなどがある
  • CSS Modulesは標準のCSSの記法で書けるため導入しやすいが、JestのSnapshotテストで使用することができない
  • style属性はReact公式で非推奨なので、上記を考えるとCSS-in-JSを採用するのがいいと判断した

目次

Reactのスタイリングのいろいろ
CSS ModulesはJestのSnapshotテストと相性が悪い
CSS-in-JS(styled-components)でスナップショットテスト
まとめ

Reactのスタイリングのいろいろ

Reactのスタイリングには様々な手法があります。
例えば

style属性にCSSPropertiesオブジェクトを渡すパターン

style属性に渡すパターン.tsx
import React, { CSSProperties, FC } from "react";

export const Button: FC = () => {
    return (
        <span style={style}>
            <span>ボタン</span>
        </span>
    );
};

const style: CSSProperties = {
    alignItems: "center",
    backgroundColor: "orange",
    cursor: "pointer",
    display: "inline-flex",
    fontSize: "14px",
    fontWeight: "bold",
};

CSS Modulesを使用するパターン

CSSModulesを使用した例.tsx
import styles from "./button.module.css";

export const Button: FC = () => {
    return (
        <span className={styles.button}>
            <span>ボタン</span>
        </span>
    );
};
button.module.css
.button {
    align-items: center;
    background-color: orange;
    cursor: pointer;
    display: inline-flex;
    font-size: 14px;
    font-weight: bold;
}

CSS-in-JSを使用するパターン

※CSS in JSには様々なツールがありますが、今回はstyled-componentsを使用した例を紹介します。

styled-componentsを使用した例.tsx
import React, { FC } from "react";
import styled from "styled-components";

export const TextButton: FC = () => {
    return (
        <Body>
            <span>ボタン</span>
        </Body>
    );
};

const Body = styled.span`
    align-items: center;
    background-color: orange;
    cursor: pointer;
    display: inline-flex;
    font-size: 14px;
    font-weight: bold;
`;

このようにReactでは、スタイリングだけで様々な手法があるので、サービス開発をする上でどのスタイリング手法を選択するべきか開発者の頭を悩ませます。

CSS ModulesはJestのSnapshotテストと相性が悪い

数多あるスタイリング手法の中で、自分は元々CSS Modulesを採用していました。

  • 通常のCSSの記法で書けるため、学習コストが低い
  • スコープをコンポーネント内部に閉じ込めることができる

ことが主な理由でした。

ですが、各コンポーネントごとでJestのスナップショットテストを実施しようとすると下記のようなエラーになります。

Button.spec.tsx
import React from "react";
import renderer from "react-test-renderer";
import { Button } from "../Button";

test("Button", () => {
    const component = renderer.create(<Button />);
    const tree = component.toJSON();

    expect(tree).toMatchSnapshot();
});

jestだとCSS Modulesを読み込むことができないため起きるエラーです。
jest-css-modulesというライブラリを使用してjest.config.js内に下記のような設定をするとエラーの回避はできるようになります。

jest.config.js
module.exports = {
    ...
    moduleNameMapper: {
      "\\.(css|less|scss|sss|styl)$": "<rootDir>/node_modules/jest-css-modules",
    },
    ...
};

ですが、このライブラリはjest内でバンドルする際にCSS Modulesを読み込んでいるわけではなく、単に上記エラーを回避しているだけなので、CSS Modulesのスタイルを変更しても、テストを通過してしまいます。

button.module.css
.button {
    align-items: center;
    /* orangeをredに変更 */
    background-color: red;
    cursor: pointer;
    display: inline-flex;
    font-size: 14px;
    font-weight: bold;
}

※スタイリング変更後もスナップショットテストが通ってしまう

スナップショットテストでスタイリングが反映されていないのはほとんど意味がないと思い、スタイリングの方式を切り替えることにしました。

CSS-in-JS(styled-components)でスナップショットテスト

他の選択肢としてはstyle属性を使ったスタイリングもありえますが、公式で非推奨とされています。

Some examples in the documentation use style for convenience, but using the style attribute as the primary means of styling elements is generally not recommended.
便宜上style属性をドキュメント内で使用していますが、要素のスタイリングとしてスタイル属性を使うことは一般的に推奨されていません。

そのため、CSS-in-JS(styled-components)を採用したいと思いますが、その前に、Snapshotテストでスタイリングの変更が検知できるか確認をします。

styled-componentsを使用した例(変更前).tsx
import React, { FC } from "react";
import styled from "styled-components";

export const TextButton: FC = () => {
    return (
        <Body>
            <span>ボタン</span>
        </Body>
    );
};

const Body = styled.span`
    align-items: center;
    background-color: orange;
    cursor: pointer;
    display: inline-flex;
    font-size: 14px;
    font-weight: bold;
`;

これでスナップショットを取り、

スタイルを少し変更します。

styled-componentsを使用した例(変更後).tsx
import React, { FC } from "react";
import styled from "styled-components";

export const TextButton: FC = () => {
    return (
        <Body>
            <span>ボタン</span>
        </Body>
    );
};

const Body = styled.span`
    align-items: center;
    /* orangeをredに変更 */
    background-color: red;
    cursor: pointer;
    display: inline-flex;
    font-size: 14px;
    font-weight: bold;
`;

スナップショットテストが失敗したことが確認できました。

ただ、この差分の内容だと、styled-componentsが自動生成したclassNameが変わっていることは確認できますが、具体的なスタイルの変更内容までは分かりません。
それでも、自分が行ったスタイルの修正がどのコンポーネントに影響を及ぼしているのか分かると、スナップショットテストの有用性は高まると考えています。

まとめ

Reactのスタイリング手法は、CSS-in-JSだけでも今回紹介したstyled-components以外にstyled-jsxなどがあったり、本当に様々な選択肢があります。
今回はスナップショットテストとの相性という観点でのスタイリング手法の選択の意思決定をまとめてみました。

またpros,cons比較すると違う選択もありえるかもしれないので、その際にはまたアップデートして記事として紹介したいと思います。