Как смоделировать одну и ту же функцию, вызванную в разных компонентах с разными возвращаемыми значениями - PullRequest
0 голосов
/ 20 апреля 2020

Мне нужно смоделировать функцию useAxios, но она вызывается в двух разных компонентах, и один из этих компонентов используется внутри другого. Это мой код:

import React, { useEffect, useState } from 'react'
import useAxios from 'axios-hooks'
import { Table, Space } from 'antd'
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
import { faEdit, faCalendar, faUserPlus, faTimes } from '@fortawesome/free-solid-svg-icons'

export const RemoveProjectButton = ({ project, updateProjects }) => {
  const [, execute] = useAxios(
    {
      url: `${process.env.REACT_APP_API_URL}/projects/${project.key}/`,
      method: 'delete'
    },
    {
      manual: true
    }
  )
  const removeProject = async (project) => {
    await execute()
    updateProjects(project)
  }

  return <a data-testid={`project-${project.key}`} onClick={() => { removeProject(project) }}><FontAwesomeIcon icon={faTimes} /></a>
}

export const Projects = () => {
  const [projects, setProjects] = useState([])
  const [{ data, loading, error }] = useAxios(
    `${process.env.REACT_APP_API_URL}/projects/`
  )

  useEffect(() => {
    setProjects(data)
  }, [data])

  useEffect(() => {}, [projects])

  const updateProjects = (projectToDelete) => {
    setProjects(() => projects.filter(project => project.key !== projectToDelete.key))
  }

  if (loading) return <p data-testid='loading'>Loading...</p>
  if (error) return <p data-testid='error'>Error!</p>

  const columns = [
    {
      title: 'Title',
      dataIndex: 'title',
      key: 'title',
      render: title => <a>{title}</a>
    },
    {
      title: 'Start Date',
      dataIndex: 'startDate',
      key: 'startDate'
    },
    {
      title: 'Description',
      dataIndex: 'description',
      key: 'description',
      render: description => `${description.substring(0, 50)}...`
    },
    {
      title: 'Team',
      dataIndex: 'team',
      key: 'team'
    },
    {
      title: 'Action',
      key: 'action',
      render: (text, record, index) => (
        <Space size='middle'>
          <FontAwesomeIcon icon={faEdit} />
          <FontAwesomeIcon icon={faCalendar} />
          <FontAwesomeIcon icon={faUserPlus} />
          <RemoveProjectButton project={record} updateProjects={updateProjects} />
        </Space>
      )
    }
  ]

  return (
    <Table
      data-testid='project-table'
      columns={columns}
      dataSource={projects}
      pagination={false}
    />
  )
}

и это мой тест:

import React from 'react'
import { render, cleanup, fireEvent } from '@testing-library/react'
import { Projects, RemoveProjectButton } from '../Projects'
import useAxios from 'axios-hooks'
jest.mock('axios-hooks')

const TABLE_TEST_ID = 'project-table'
const fakeData = [
  {
    key: 1,
    title: 'Testing Project Alpha',
    startDate: '2020-04-18',
    description: 'This is just for testing',
    team: 'A, B, C'
  },
  {
    key: 2,
    title: 'Testing Project Beta',
    startDate: '2020-04-19',
    description: 'This is just for testing too',
    team: 'X, Y, Z'
  }
]

describe('projects table', () => {
  let projects

    beforeEach(() => {
      projects = JSON.parse(JSON.stringify(fakeData))
      useAxios.mockReturnValue([{
        data: projects,
        loading: false,
        error: null
      }])
    })

    it('removes project when clicking on X button in row', async () => {
      const { getByTestId, queryByTestId } = render(<Projects />)
      const executeMock = jest.fn()

      useAxios.mockReturnValue([{
        data: projects,
        loading: false,
        error: null
      }])
      .mockReturnValue([{}, executeMock])
      .mockReturnValue([{}, executeMock])



      expect(getByTestId(TABLE_TEST_ID)).toHaveTextContent('Testing Project Alpha')
      await fireEvent.click(getByTestId('project-1'))
      expect(queryByTestId('project-1')).toBeNull()
      expect(getByTestId(TABLE_TEST_ID)).not.toHaveTextContent('Testing Project Alpha')
    })
})

Однако я получаю следующую ошибку:

TypeError: execute is not a function

      16 |   )
      17 |   const removeProject = async (project) => {
    > 18 |     await execute()
         |           ^
      19 |     updateProjects(project)
      20 |   }
      21 |

Я понимаю, что проблема в том, что я не передаю правильный макет для компонента RemoveProjectButton. Однако я понятия не имею, как мне этого добиться, потому что useAxios вызывается в разных компонентах и ​​должно иметь разные возвращаемые значения. Я также пытался использовать mockImplementationOnce, но кажется, что компонент Projects визуализируется несколько раз перед рендерингом компонента RemoveProjectButton, поэтому я чувствую, что мне нужно угадать, сколько раз мне придется использовать mockImplementationOnce.

Ответы [ 2 ]

1 голос
/ 20 апреля 2020

Возможно, просто используйте mockImplementation , тогда вы можете вернуть другое значение в соответствии с параметрами.

useAxios.mockImplementation((...args) => {
  if (match1(args)) {
    return result1;
  }
  if (match2(args)) {
    return result2;
  }
  return result3;
});
0 голосов
/ 21 апреля 2020

Я воспользовался подходом, предложенным @ ответом Мироне , но у меня возникла другая проблема после того, как проблема с издевательством была решена, поэтому я добавлю свой полный ответ на случай, если кто-то еще столкнется с тем же:

it('removes project in table when clicking on X', async () => {
  const executeMock = jest.fn()
  useAxios.mockImplementation((...args) => {
    // This mocking was inspired on Mirone's answer
    switch (args.length) {
      case 1:
        return [{
          data: projects,
          loading: false,
          error: null
        }]
      case 2:
        return [{}, executeMock]
      default: break
    }
  })
  const { getByTestId, findByTestId } = render(<Projects />)
  expect(getByTestId('project-table')).toHaveTextContent('Testing Project Alpha')
  fireEvent.click(getByTestId('project-1'))
  const table = await findByTestId('project-table')
  expect(table).not.toHaveTextContent('Testing Project Alpha')
})

После того, как проблема с насмешками была решена, я получил сообщение об ошибке:

Предупреждение. Обновление проектов внутри теста не было включено в действие (...).

Эта проблема была решена путем добавления:

const table = await findByTestId('project-table')

Предыдущая строка ожидала, пока элемент с data-testid="project-table" был перерисован, но я получил еще одну ошибку:

MutationObserver не является конструктором

Я обнаружил проблему на Github , которая предоставила решение для этого. Вкратце, проблема в том, что CRA не использует последнюю версию jsdom, поэтому решение было установить jest-environment-jsdom-sixteen следующим образом:

yarn add jest-environment-jsdom-sixteen --dev

, а также установить его в package.json :

"scripts": {
   ...
   "test": "react-scripts test --env=jest-environment-jsdom-sixteen",
   ...
}

Надеюсь, мой ответ поможет кому-то еще, кто может столкнуться с такими же проблемами.

...