无法触发使用Jest和酶的测试功能

时间:2019-11-16 23:24:21

标签: reactjs jestjs enzyme react-select

我刚开始测试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帖子,有趣的文档以及关于Stackoverflow的至少3个问题,但是我仍然无法弄清楚。

感谢任何帮助!

FirstQuestion:)

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

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');
});

但是change事件似乎并没有更新输入中的值。测试失败的消息表明它正在接收'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)

1 个答案:

答案 0 :(得分:1)

问题与

  selector.handleInputChange = myF;

实际上,您不能以此方式模拟某些内部变量。如果这是基于类的组件中的方法,则可以按照

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');
});