Jest Styled Components メモ

Styled components を使ったコンポーネントをテストする場合、どのスタイルが変更されたかの検知はできません。

例えば、下のような styled components で作成されたボタンを Jest の snapshot 機能 + react-testing-library でテストすることを考えてみます。

通常のボタンの状態と、props に isError を渡すことでボタンの状態が変更されているかのテストです。

describe("button", () => {
  it("normal", () => {
    const { container } = render(<Button>ボタン</Button>);
    expect(container.firstChild).toMatchInlineSnapshot(`
    <button
      class="sc-bdVaJa dwVJQt"
    >
      ボタン
    </button>
    `);
  });

  it("error", () => {
    const { container } = render(<Button isError={true}>ボタン</Button>);
    expect(container.firstChild).toMatchInlineSnapshot(`
    <button
      class="sc-bdVaJa ANkxh"
    >
      ボタン
    </button>
    `);
  });
});

snapshot で render された内容を見てみるとクラス名が dwVJQt から ANkxh に変わっています。

ここから normal に適用されているスタイルとは違うスタイルがあたっていることがわかりますが、本当に期待しているスタイルが当たっているかまではこのテスト結果からはわかりません。

Jest Styled Components の利用

そこで、 Jest Styled Components を利用してみます

GitHub - styled-components/jest-styled-components: 🔧 💅 Jest utilities for Styled Components

さっそくテストを書き換えて結果を見ていきます。

describe("button", () => {
  it("normal", () => {
    const { container } = render(<Button>ボタン</Button>);
    expect(container.firstChild).toMatchInlineSnapshot(`
      .c0 {
        border: 2px solid white;
      }

      <button
        class="c0"
      >
        ボタン
      </button>
    `);
  });

  it("error", () => {
    const { container } = render(<Button isError={true}>ボタン</Button>);
    expect(container.firstChild).toMatchInlineSnapshot(`
      .c0 {
        border: 2px solid tomato;
      }

      <button
        class="c0"
      >
        ボタン
      </button>
    `);
  });
});

適用されているスタイルがスナップショット内に表示されるようになりました。 他にもクラス名が c0 という文字列に置き換わっています。

これによって、クラス名の変更が差分として表出しなくなり、スタイルと HTML の差分だけに注視すれば良くなります。

導入方法

npm または yarn でプロジェクトに Jest Styled Components をインストールします。

$ npm install jest-styled-components --save-dev

テストファイル中で読み込みます。

import "jest-styled-components"

これで styled-components のスナップショットを作成した際に Jest Styled Components の利用 の項で解説したようなテスト結果を得られるようになります。

全体に Jest Styled Components を適用する

一つひとつに import して追加するのが面倒であれば jest.config.js を使ってテスト実行前に読むこむ処理を追加します。

module.exports = {
  setupFilesAfterEnv: ["./jest.setup.js"],
};

これでテスト実行前に setupFilesAfterEnv で指定している ./jest.setup.js が実行されるので、 jest.setup.jp 内で import する処理を追記しておきます。

一つひとつのテストで import しなくても Jest Styled Components を実行することができるようになります。

toHaveStyleRule

Jest Styled Components を適用すると Jest で toHaveStyleRule という matcher が使えるようになります。

const { container } = renderer(<Button />)
expect(container.firstChild).toHaveStyleRule("color", "tomato")

擬似クラス、 media query のスタイル

toHaveStyleRule の第三引数を利用すると擬似クラスや media query の指定が可能です。 (現状は React のコンポーネントのみ可能)

const { container } = render(<Button isError={true}>ボタン</Button>);

describe("button", () => {
  expect(container.firstChild).toHaveStyleRule("color", "tomato",  {
    media: "(max-width:640px)",
    modifier: ":hover",
  })
})

別のコンポーネントに依存したスタイル

別の styled-component のスタイルをネストしている場合は、 modifier オプションで Styled Components の css helper を使います。 これで toHaveStyleRule のテストが行なえます。

const Button = styled.button`
  color: white;
`

const List = styled.div`
  ${Button} {
    margin: 1em;
  }
`

describe("list", () => {
  it("リストにネストされたボタンはマージンが適用される", () => {
    const { container } = render(<List><Button /></List>)
    expect(container.firstChild).toHaveStyleRule("margin", "1em", {
      modifier: css`${Button}`,
    })
  })
})

github.com