【问题标题】:Unable to trigger function for testing using Jest and Enzyme无法触发使用 Jest 和 Enzyme 进行测试的功能
【发布时间】:2020-03-12 17:39:38
【问题描述】:

我是测试 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 个问题,但我仍然无法弄清楚。

感谢任何帮助!

第一个问题:)

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

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)

【问题讨论】:

    标签: reactjs jestjs enzyme react-select


    【解决方案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');
    });
    
    

    【讨论】:

    • 谢谢@skyboyer!这种策略很有意义。我尝试了更改并且确实调用了搜索函数,但没有调用 5。它使用输入的初始值调用:添加到问题的详细信息。
    • 可能你的测试发现了一个错误 :) 没有AsyncSelect 的代码很难说,但在我看来,该组件只是在输入事件中调用props.loadOptions() 并且在那一刻回调指的是旧的searchTerm。我的猜测:在真正的应用程序中,该组件的行为就像搜索先前的值一样,落后了一步。
    • 再次感谢,非常感谢您的帮助!
    猜你喜欢
    • 2022-08-10
    • 2021-06-24
    • 1970-01-01
    • 2017-08-12
    • 1970-01-01
    • 2020-11-24
    • 1970-01-01
    • 2019-06-16
    • 2018-11-09
    相关资源
    最近更新 更多