5
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

Jest+EnzymeでuseContextをテストする

Last updated at Posted at 2019-10-05

Jest+EnzymeでReactコンポーネントをテストしようと思ったらshallowuseContextに対応していなかったので、その対処法のメモです。

結論

useContextをカスタムhookでラップして、spyOnでモック化する

説明

うまくいかないパターン

StudentListという、生徒の情報一覧を表示するコンポーネントを想定します。

types.ts
export interface Student {
  id: number
  name: string
  gender: 'male' | 'female'
}
StudentsContext.tsx
import React from 'react'
import { Student } from './types'

interface ProviderProps {
  students?: Student[]
}

export const StudentsContext = React.createContext<Student[]>([])
export const StudentsContextProvider: React.FC<ProviderProps> = ({students = [], ...props}) => (
  <StudentsContext.Provider value={students} {...props} />
)
StudentList.tsx
import React from 'react'
import { StudentsContext } from './StudentsContext'

export const StudentList: React.FC = () => {
  const students = React.useContext(StudentsContext)
  return (
    <ul>
      {students.map(student => (
        <li key={student.id}>
          {student.name}({student.gender})
        </li>
      ))}
    </ul>
  )
}

これをテストしてみます。

StudentList.test.tsx
import React from 'react'
import { shallow } from 'enzyme'
import { StudentList } from './StudentList'
import { StudentsContextProvider } from './StudentsContext'
import { Student } from './types'

const studentsVal: Student[] = [
  { id: 1, name: 'name1', gender: 'male' },
  { id: 2, name: 'name2', gender: 'female' },
]

describe('StudentList component', () => {
  it('should render all students', () => {
    const wrapper = shallow(
      <StudentsContextProvider students={studentsVal}>
        <StudentList />
      </StudentsContextProvider>
    )
    const items = wrapper.dive().dive().find('li')
    expect(items.length).toBe(2)
  })
})

コケました

 ● StudentList component › should render all students

    expect(received).toBe(expected) // Object.is equality

    Expected: 2
    Received: 0

StudentList.tsx内のReact.useContext(StudentsContext)が正しく実行できていないことが原因です。

うまくいくパターン

useContextの処理を、カスタムhookでラップして、そのカスタムhookをモック化する形で対応していきます。

StudentsContext.tsx
import React from 'react'
import { Student } from './types'

interface ProviderProps {
  students?: Student[]
}

const StudentsContext = React.createContext<Student[]>([])
// useStudentsContextというカスタムhookでuseContextをラップする
export const useStudentsContext = () => React.useContext(StudentsContext)
export const StudentsContextProvider: React.FC<ProviderProps> = ({students = [], ...props}) => (
  <StudentsContext.Provider value={students} {...props} />
)
StudentList.tsx
import React from 'react'
import { useStudentsContext } from './StudentsContext'

export const StudentList: React.FC = () => {
  // useContextではなくカスタムhookで取得するようにする
  const students = useStudentsContext()
  return (
    <ul>
      {students.map(student => (
        <li key={student.id}>
          {student.name}({student.gender})
        </li>
      ))}
    </ul>
  )
}
StudentList.test.tsx
import React from 'react'
import { shallow } from 'enzyme'
import { StudentList } from './StudentList'
// spyOnに渡すために`* as ~`の形でインポート
import * as StudentsContextObj from './StudentsContext'
import { Student } from './types'

const studentsVal: Student[] = [
  { id: 1, name: 'name1', gender: 'male' },
  { id: 2, name: 'name2', gender: 'female' },
]

describe('StudentList component', () => {
  it('should render all students', () => {
    // useStudentsContextをモック化
    jest.spyOn(StudentsContextObj, 'useStudentsContext')
      .mockImplementation(() => studentsVal)

    // そしてもはやProviderも不要になる
    const wrapper = shallow(<StudentList />)
    const items = wrapper.find('li')
    expect(items.length).toBe(2)
  })
})

これでテストが通るようになります。

spyOnについて

ドキュメント:
https://deltice.github.io/jest/docs/ja/jest-object.html#jestspyonobject-methodname

spyOnの第一引数はObject、第二引数はそのメソッドなので、それに合わせて

import * as StudentsContext from './StudentsContext'

のような形でインポートするところがポイントです。

ちなみに今回の場合、useStudentsContextのカスタムhookを作らなくても

jest.spyOn(React, 'useContext').mockImplementation(() => studentsVal)

で代替できますが、この場合当然全てのuseContextがモック化されてしまうので、避けたほうが良いでしょう。

5
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?