Как я могу протестировать компонент React Hooks, изменив useState - PullRequest
0 голосов
/ 09 февраля 2020

У меня есть функциональный компонент React-хуков, который я хотел бы протестировать с помощью Jest / Enzyme. Я хотел бы проверить его третичное поведение рендеринга на основе значения useState. Я не могу найти ни одного примера в Интернете. Здесь нет «клика» для симуляции - нет вызова API для имитации, потому что в конце мне все еще нужно протестировать на основе значения useState. В прошлом с компонентами класса я мог устанавливать состояние. С новыми крючками я не могу. Итак, в основном - как я могу смоделировать asyn c, ожидающий внутри смоделированной функции submitForm, чтобы рендер работал правильно?

Вот мой компонент:

import React, { useState } from 'react';
import { Redirect } from 'react-router-dom';

import Form from 'core/Form';

export const Parent = ({submitForm}) => {
  const [formValues, setFormValues] = useState({});
  const [redirect, setRedirect] = useState(false);

  const handleChange = name => evt => {
    setFormValues({ ...formValues, [name]: evt.target.value });
  };

  const onSubmit = async () => {
      try {
        const res = await submitForm(formValues);
        if (res) setRedirect(true);
        else setRedirect(false);
      } catch (err) {
        console.log('Submit error: ', err);
      }
  };

  return redirect ? (
    <Redirect push to={path} />
  ) : (
    <Form onSubmit={onSubmit} values={formValues} onChange={handleChange} />
  );
};

export default Parent;

Вот мое тестирование до сих пор :

import React from 'react';
import { shallow } from 'enzyme';
import { Redirect } from 'react-router-dom';

import Parent from './Parent';
import Form from 'core/Form';

let wrapper, props;
.
.
.

describe('<Parent /> rendering', () => {
  beforeEach(() => {
    props = createTestProps();
    wrapper = shallow(<Parent {...props} />);
  });

  afterEach(() => {
    jest.clearAllMocks();
  });

  const setState = jest.fn();
  const useStateSpy = jest.spyOn(React, 'useState');
  useStateSpy.mockImplementation(init => [init, setState]);

  it('Should render 1 Form', () => {
    expect(wrapper.find(Form)).toHaveLength(1);
  });

  it('renders Redirect after API call', () => {
    setRedirect = jest.fn(() => false);

    expect(wrapper.find(Redirect)).toHaveLength(1);
  });

  it('renders Form before API call', () => {
    setRedirect = jest.fn(() => true);

    expect(wrapper.find(Form)).toHaveLength(1);
  });
});

1 Ответ

2 голосов
/ 10 февраля 2020

Вам не нужно шпионить useState крюк. Это означает, что вы не должны тестировать эти хуки и методы компонента напрямую. Вместо этого вы должны проверить поведение компонентов (state, props и то, что отображается)

Например:

index.tsx:

import React, { useState } from 'react';
import { Redirect } from 'react-router-dom';

export const Form = ({ onSubmit, onChange, values }) => <form onSubmit={onSubmit}></form>;
const path = '/user';

export const Parent = ({ submitForm }) => {
  const [formValues, setFormValues] = useState({});
  const [redirect, setRedirect] = useState(false);

  const handleChange = (name) => (evt) => {
    setFormValues({ ...formValues, [name]: evt.target.value });
  };

  const onSubmit = async () => {
    try {
      const res = await submitForm(formValues);
      if (res) setRedirect(true);
      else setRedirect(false);
    } catch (err) {
      console.log('Submit error: ', err);
    }
  };

  return redirect ? (
    <Redirect push to={path} />
  ) : (
    <Form onSubmit={onSubmit} values={formValues} onChange={handleChange} />
  );
};

export default Parent;

index.test.tsx:

import Parent, { Form } from './';
import React from 'react';
import { shallow } from 'enzyme';
import { Redirect } from 'react-router-dom';
import { act } from 'react-dom/test-utils';

const whenStable = async () =>
  await act(async () => {
    await new Promise((resolve) => setTimeout(resolve, 0));
  });

describe('60137762', () => {
  it('should render Form', () => {
    const props = { submitForm: jest.fn() };
    const wrapper = shallow(<Parent {...props}></Parent>);
    expect(wrapper.find(Form)).toBeTruthy();
  });

  it('should handle submit and render Redirect', async () => {
    const props = { submitForm: jest.fn().mockResolvedValueOnce(true) };
    const wrapper = shallow(<Parent {...props}></Parent>);
    wrapper.find(Form).simulate('submit');
    await whenStable();
    expect(props.submitForm).toBeCalledWith({});
    expect(wrapper.find(Redirect)).toBeTruthy();
  });

  it('should handle submit and render Form', async () => {
    const props = { submitForm: jest.fn().mockResolvedValueOnce(false) };
    const wrapper = shallow(<Parent {...props}></Parent>);
    wrapper.find(Form).simulate('submit');
    await whenStable();
    expect(props.submitForm).toBeCalledWith({});
    expect(wrapper.find(Form)).toBeTruthy();
  });

  it('should handle error if submit failure', async () => {
    const logSpy = jest.spyOn(console, 'log');
    const mError = new Error('network');
    const props = { submitForm: jest.fn().mockRejectedValueOnce(mError) };
    const wrapper = shallow(<Parent {...props}></Parent>);
    wrapper.find(Form).simulate('submit');
    await whenStable();
    expect(props.submitForm).toBeCalledWith({});
    expect(logSpy).toHaveBeenCalledWith('Submit error: ', mError);
  });
});

Результаты модульных испытаний с отчетом о покрытии:

 PASS  stackoverflow/60137762/index.test.tsx
  60137762
    ✓ should render Form (18ms)
    ✓ should handle submit and render Redirect (15ms)
    ✓ should handle submit and render Form (8ms)
    ✓ should handle error if submit failure (18ms)

  console.log node_modules/jest-environment-enzyme/node_modules/jest-mock/build/index.js:866
    Submit error:  Error: network
        at /Users/ldu020/workspace/github.com/mrdulin/react-apollo-graphql-starter-kit/stackoverflow/60137762/index.test.tsx:39:20
        at step (/Users/ldu020/workspace/github.com/mrdulin/react-apollo-graphql-starter-kit/stackoverflow/60137762/index.test.tsx:44:23)
        at Object.next (/Users/ldu020/workspace/github.com/mrdulin/react-apollo-graphql-starter-kit/stackoverflow/60137762/index.test.tsx:25:53)
        at /Users/ldu020/workspace/github.com/mrdulin/react-apollo-graphql-starter-kit/stackoverflow/60137762/index.test.tsx:19:71
        at new Promise (<anonymous>)
        at Object.<anonymous>.__awaiter (/Users/ldu020/workspace/github.com/mrdulin/react-apollo-graphql-starter-kit/stackoverflow/60137762/index.test.tsx:15:12)
        at Object.<anonymous> (/Users/ldu020/workspace/github.com/mrdulin/react-apollo-graphql-starter-kit/stackoverflow/60137762/index.test.tsx:37:47)
        at Object.asyncJestTest (/Users/ldu020/workspace/github.com/mrdulin/react-apollo-graphql-starter-kit/node_modules/jest-jasmine2/build/jasmineAsyncInstall.js:100:37)
        at resolve (/Users/ldu020/workspace/github.com/mrdulin/react-apollo-graphql-starter-kit/node_modules/jest-jasmine2/build/queueRunner.js:43:12)
        at new Promise (<anonymous>)
        at mapper (/Users/ldu020/workspace/github.com/mrdulin/react-apollo-graphql-starter-kit/node_modules/jest-jasmine2/build/queueRunner.js:26:19)
        at promise.then (/Users/ldu020/workspace/github.com/mrdulin/react-apollo-graphql-starter-kit/node_modules/jest-jasmine2/build/queueRunner.js:73:41)

-----------|---------|----------|---------|---------|-------------------
File       | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s 
-----------|---------|----------|---------|---------|-------------------
All files  |   78.57 |      100 |      40 |   93.75 |                   
 index.tsx |   78.57 |      100 |      40 |   93.75 | 12                
-----------|---------|----------|---------|---------|-------------------
Test Suites: 1 passed, 1 total
Tests:       4 passed, 4 total
Snapshots:   0 total
Time:        3.716s, estimated 5s

Исходный код: https://github.com/mrdulin/react-apollo-graphql-starter-kit/tree/master/stackoverflow/60137762

...