无法触发使用 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');
});
我是测试 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');
});