无法触发使用 Jest 和 Enzyme 进行测试的功能

Unable to trigger function for testing using Jest and Enzyme

我是测试 React 的新手,我正在尝试测试当用户更改 AsyncSelect 组件(通过它的 onInputChange 回调连接)上的输入值时调用 HotelSelect.handleInputChange。

HotelSelect.js

import React, { useEffect, useState } from 'react';
import HotelSearchApi from '../api/HotelSearchApi';
import AsyncSelect from 'react-select/async';
import './HotelSelect.css';

export default function HotelSelect({
  ...props
}) {
  const [searchTerm, setSearchTerm] = useState('');
  const [selectValue, setSelectValue] = useState({value: '', label: ''});

  useEffect(() => {
    setSelectValue({value: props.hotel.brand_code, label: props.hotel.brand_code});
  }, [props.hotel])

  function formatHotelList(hotels) {
    return hotels.map(function(hotel) {
      return {...hotel, ...{ value: hotel.brand_code, label: hotel.brand_code } }
    });
  }

  function handleInputChange(newTerm) {
    setSearchTerm(newTerm);
  }

  async function loadOptions() {
    let hotels =  await HotelSearchApi.search(searchTerm);
    return formatHotelList(hotels);
  }

  return (
    <AsyncSelect cacheOptions={true}
                 className="HotelSelect"
                 defaultInputValue={props.hotel.brand_code}
                 value={selectValue}
                 loadOptions={loadOptions}
                 onChange={props.changeHotel}
                 onInputChange={handleInputChange}/>
  )
};

HotelSelect.test.js

import React from 'react';
import HotelSelect from './HotelSelect';
import AsyncSelect from 'react-select/async';
import renderer from 'react-test-renderer';
import { mount, shallow, render } from 'enzyme';

const hotel = {
  brand_code: '12345'
}

const searchData = {
  data: [
    {
      id: '123',
      type: 'hotel',
      attributes: {
        brand_code: '55555',
        name: 'A Hotel'
      }
    }
  ]
}

it('renders correctly', () => {
  const selector = renderer
    .create(<HotelSelect hotel={hotel} />)
    .toJSON();
  expect(selector).toMatchSnapshot();
});

it('should update searchTerm as user changes input', () => {
  const myF = jest.fn();
  const selector = mount(<HotelSelect hotel={hotel} />);
  selector.handleInputChange = myF;

  let input = selector.find('input');
  input.simulate('change', { target: { value: '5' } });

  expect(myF).toHaveBeenCalled();
});

当我使用 console.log 检查选择器时,它看起来像是附加了模拟函数:

  ● Console

    console.log src/components/HotelSelect.test.js:38
      ReactWrapper {
        handleInputChange: [Function: mockConstructor] {
          _isMockFunction: true,
          getMockImplementation: [Function],
          mock: [Getter/Setter],
          mockClear: [Function],
          mockReset: [Function],
          mockRestore: [Function],
          mockReturnValueOnce: [Function],
          mockResolvedValueOnce: [Function],
          mockRejectedValueOnce: [Function],
          mockReturnValue: [Function],
          mockResolvedValue: [Function],
          mockRejectedValue: [Function],
          mockImplementationOnce: [Function],
          mockImplementation: [Function],
          mockReturnThis: [Function],
          mockName: [Function],
          getMockName: [Function]
        }
      }

不幸的是,该功能似乎没有被触发:

  ● should update searchTerm as user changes input

    expect(jest.fn()).toHaveBeenCalled()

    Expected number of calls: >= 1
    Received number of calls:    0

      37 |   input.simulate('change', { target: { value: '5' } });
      38 |   console.log(selector);
    > 39 |   expect(myF).toHaveBeenCalled();
         |               ^
      40 | });
      41 | 

      at Object.<anonymous> (src/components/HotelSelect.test.js:39:15)

我不能在输入框上触发 onChange 事件,它应该在 AsyncSelect 组件上触发 onInputChange 回调。

我已经阅读了一些在线的 TDD 帖子、开玩笑的文档以及关于 Whosebug 的至少 3 个问题,但我仍然无法弄清楚。

感谢任何帮助!

第一个问题:)

更新: 我将测试更改为以下内容:

import React from 'react';
import HotelSearchApi from '../api/HotelSearchApi';
import HotelSelect from './HotelSelect';
import AsyncSelect from 'react-select/async';
import renderer from 'react-test-renderer';
import { mount, shallow, render } from 'enzyme';

const hotel = {
  brand_code: '12345'
}

const searchData = {
  data: [
    {
      id: '123',
      type: 'hotel',
      attributes: {
        brand_code: '55555',
        name: 'A Hotel'
      }
    }
  ]
}

jest.mock('../api/HotelSearchApi');

it('renders correctly', () => {
  const selector = renderer
    .create(<HotelSelect hotel={hotel} />)
    .toJSON();
  expect(selector).toMatchSnapshot();
});

it('should update searchTerm as user changes input', () => {
  const selector = mount(<HotelSelect hotel={hotel} />);

  let input = selector.find('input');
  expect(HotelSearchApi.search).not.toHaveBeenCalled();
  input.simulate('change', { target: { value: '5' } });

  expect(HotelSearchApi.search).toHaveBeenCalledWith('5');
});

但更改事件似乎并未更新输入中的值。失败的测试消息说它正在接收“12345”,这是初始值 (hotel.brand_code):

  ● should update searchTerm as user changes input

    expect(jest.fn()).toHaveBeenCalledWith(...expected)

    Expected: "5"
    Received: "12345"

    Number of calls: 1

      39 |   input.simulate('change', { target: { value: '5' } });
      40 | 
    > 41 |   expect(HotelSearchApi.search).toHaveBeenCalledWith('5');
         |                                 ^
      42 | });
      43 | 

      at Object.<anonymous> (src/components/HotelSelect.test.js:41:33)

with

的问题
  selector.handleInputChange = myF;

实际上你不能用这种方式模拟一些内部变量。如果那是基于 class 的组件中的方法,您可以像

HotelSelect.instance().handleInputChange = jest.fn();

但无论如何这都是一个糟糕的举动。你知道吗?您实际上不需要模拟内部方法。

检查调用了哪些内部方法没有价值。您只需要确保最终调用了一些外部 API 并使用预期参数:

import HotelSearchApi from '../api/HotelSearchApi';

jest.mock('../api/HotelSearchApi'); // automock

...
it('should update searchTerm as user changes input', () => {
  const selector = mount(<HotelSelect hotel={hotel} />);

  let input = selector.find('input');
  expect(HotelSearchApi.search).not.toHaveBeenCalled();
  input.simulate('change', { target: { value: '5' } });

  expect(HotelSearchApi.search).toHaveBeenCalledWith('5');
});