Reactjs 在测试期间模拟表单提交时,不会调用在Redux连接组件中作为道具传递的操作

Reactjs 在测试期间模拟表单提交时,不会调用在Redux连接组件中作为道具传递的操作,reactjs,unit-testing,react-redux,jestjs,enzyme,Reactjs,Unit Testing,React Redux,Jestjs,Enzyme,我正在测试我的第一个应用程序,在测试Redux连接的组件时遇到了问题 更具体地说,我正在测试Search.js。这个想法是在子组件DisplaySearcgBar.js中模拟表单提交,然后测试是否调用了setAlert和getRestaurants 在测试#3中,由于提交表单时输入是空的,Search.js应该调用OnSubmit(),它应该调用setAlert,在测试#4中,它应该调用getRestaurants,因为提供了输入 两项测试均被拒绝,但出现相同错误: Search › 3 - s

我正在测试我的第一个应用程序,在测试Redux连接的组件时遇到了问题

更具体地说,我正在测试
Search.js
。这个想法是在子组件
DisplaySearcgBar.js
中模拟表单提交,然后测试是否调用了
setAlert
getRestaurants

在测试#3中,由于提交表单时输入是空的,
Search.js
应该调用
OnSubmit()
,它应该调用
setAlert
,在测试#4中,它应该调用
getRestaurants
,因为提供了输入

两项测试均被拒绝,但出现相同错误:

Search › 3 - setAlert called if search button is pressed with no input

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

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

      37 |     wrapper.find('[data-test="search"]').simulate('click');
      38 |     //expect(store.getActions().length).toBe(1);
    > 39 |     expect(wrapper.props().children.props.props.setAlert).toHaveBeenCalled();
         |                                                           ^
      40 |   });
      41 | 
      42 |   test('4 - getRestaurant called when inputs filled and search button clicked ', () => {

      at Object.<anonymous> (src/Components/restaurants/Search/__tests__/Search.test.js:39:59)

  ● Search › 4 - getRestaurant called when inputs filled and search button clicked 

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

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

      55 |     wrapper.find('[data-test="search"]').simulate('click');
      56 | 
    > 57 |     expect(wrapper.props().children.props.props.getRestaurants).toHaveBeenCalled();
         |                                                                 ^
      58 |   });
      59 | });
      60 | 

      at Object.<anonymous> (src/Components/restaurants/Search/__tests__/Search.test.js:57:65)
以下是Github上的完整存储库:


提前感谢您的帮助

这是因为enzyme的
find()
返回html节点的集合

还记得这个老好人的错误吗

方法“simulate”将在1个节点上运行

请这样尝试:
wrapper.find(“…”).at(0)

此外,当您希望模拟的“setAlert()
getRestaurant()
被调用时,您引用它们的方式无法让我们知道它是正确的还是错误的引用。因此,请提供相关的
debug()`结果,或者更好地模拟它们:

const mockSetAlert = jest.fn();
const mockGetRestaurants = jest.fn();

const wrapper = mount(
    <Search setAlert={mockSetAlert} getRestaurants={mockGetRestaurants} />
);

...

expect(mockSetAlert).toHaveBeenCalled();
expect(mockGetRestaurants).toHaveBeenCalled();
const mockSetAlert=jest.fn();
const mockGetRestaurants=jest.fn();
常量包装器=装入(
);
...
expect(mockSetAlert).toHaveBeenCalled();
期望(mockGetRestaurants).tohavebeincall();

这是一个简化的示例,但您可以理解…

这是因为enzyme的
find()
返回html节点的集合

还记得这个老好人的错误吗

方法“simulate”将在1个节点上运行

请这样尝试:
wrapper.find(“…”).at(0)

此外,当您希望模拟的“setAlert()
getRestaurant()
被调用时,您引用它们的方式无法让我们知道它是正确的还是错误的引用。因此,请提供相关的
debug()`结果,或者更好地模拟它们:

const mockSetAlert = jest.fn();
const mockGetRestaurants = jest.fn();

const wrapper = mount(
    <Search setAlert={mockSetAlert} getRestaurants={mockGetRestaurants} />
);

...

expect(mockSetAlert).toHaveBeenCalled();
expect(mockGetRestaurants).toHaveBeenCalled();
const mockSetAlert=jest.fn();
const mockGetRestaurants=jest.fn();
常量包装器=装入(
);
...
expect(mockSetAlert).toHaveBeenCalled();
期望(mockGetRestaurants).tohavebeincall();

这是一个简化的示例,但您知道……

Search.js是一个连接的组件。它的道具是通过mapDispatchToProps从商店里买来的。即使您模拟这些道具,生成的包装器也会从提供者的存储中获取相应的函数。因此,解决方案是检查是否已使用所需类型和负载调用了操作

test-4中的另一个问题是您没有在
事件中传递
名称
。因此,未在状态中设置值。要避免这种情况,请使用控制台调试测试

import React from 'react';
import { mount } from 'enzyme';
import configureStore from 'redux-mock-store';
import { Provider } from 'react-redux';
import thunk from 'redux-thunk';

import Search from './../Search';
import DisplaySearchBar from '../../../layout/DisplaySearchBar/DisplaySearchBar';

import {
  SET_LOADING,
  SET_ALERT,

} from '../../../../actions/types';

const mockStore = configureStore([thunk]);
const initialState = {
  restaurants: { restaurants: ['foo'], alert: null },
};
const store = mockStore(initialState);
const mockSetAlert = jest.fn();
const mockGetRestaurants = jest.fn();

const wrapper = mount(
  <Provider store={store}>
    <Search setAlert={mockSetAlert} getRestaurants={mockGetRestaurants} />
  </Provider>
);

describe('Search', () => {
  afterEach(() => {
    jest.clearAllMocks();
  });

  test('1 - renders without errors', () => {
    expect(wrapper.find(DisplaySearchBar)).toHaveLength(1);
  });

  test('2 - if restaurants clearButton is rendered', () => {
    expect(wrapper.find('[data-test="clear"]')).toBeTruthy();
  });

  test('3 - setAlert called if search button is pressed with no input', () => {
    wrapper.find('form').simulate('submit', { preventDefault: () => {} });
    const actions= store.getActions();
    const expected={
      type: SET_ALERT,
      payload: expect.objectContaining({msg:"Please fill all the inputs"})
    };
    expect(actions[0]).toMatchObject(expected);
  });

  test('4 - getRestaurant called when inputs filled and search button clicked ', () => {
    wrapper
      .find('[name="where"]')
      .at(0)
      .simulate('change', { target: { value: 'foo', name:"where" } });

    wrapper
      .find('[name="what"]')
      .at(0)
      .simulate('change', { target: { value: 'foo',name:"what" } });

    wrapper
      .find('[data-test="best_match"]')
      .at(0)
      .simulate('click');

    wrapper.find('form').simulate('submit', { preventDefault: () => {} });
    const actions= store.getActions();
    const expected={
      type: SET_LOADING,
    };
    expect(actions).toContainEqual(expected);
     });
});
从“React”导入React;
从“酶”导入{mount};
从“redux模拟存储”导入configureStore;
从'react redux'导入{Provider};
从“redux thunk”导入thunk;
从“/../Search”导入搜索;
从“../../../layout/DisplaySearchBar/DisplaySearchBar”导入DisplaySearchBar;
进口{
设置_加载,
设置警报,
}来自“../../../../actions/types”;
const mockStore=configureStore([thunk]);
常量初始状态={
餐厅:{餐厅:['foo'],警报:null},
};
常量存储=模拟存储(初始状态);
const mockSetAlert=jest.fn();
const mockGetRestaurants=jest.fn();
常量包装器=装入(
);
描述('搜索',()=>{
之后(()=>{
开玩笑。clearAllMocks();
});
测试('1-无错误呈现',()=>{
expect(wrapper.find(DisplaySearchBar)).toHaveLength(1);
});
测试('2-如果呈现了clearButton',()=>{
expect(wrapper.find('[data test=“clear”]')).toBeTruthy();
});
测试('3-如果在没有输入的情况下按下搜索按钮,则调用setAlert',()=>{
find('form').simulate('submit',{preventDefault:()=>{});
const actions=store.getActions();
预期常数={
类型:设置警报,
有效负载:expect.objectContaining({msg:“请填写所有输入”})
};
expect(操作[0])。toMatchObject(预期);
});
test('4-getRestaurant在填写输入并单击搜索按钮时调用',()=>{
包装纸
.find('[name=“where”]'))
.at(0)
.simulate('change',{target:{value:'foo',name:'where'});
包装纸
.find('[name=“what”]'))
.at(0)
.simulate('change',{target:{value:'foo',name:'what'});
包装纸
.find(“[data test=“best_match”]”)
.at(0)
.模拟(“点击”);
find('form').simulate('submit',{preventDefault:()=>{});
const actions=store.getActions();
预期常数={
类型:设置加载,
};
expect(动作)。toContainEqual(预期);
});
});

Search.js是一个连接的组件。它的道具是通过mapDispatchToProps从商店里买来的。即使您模拟这些道具,生成的包装器也会从提供者的存储中获取相应的函数。因此,解决方案是检查是否已使用所需类型和负载调用了操作

test-4中的另一个问题是您没有在
事件中传递
名称
。因此,未在状态中设置值。要避免这种情况,请使用控制台调试测试

import React from 'react';
import { mount } from 'enzyme';
import configureStore from 'redux-mock-store';
import { Provider } from 'react-redux';
import thunk from 'redux-thunk';

import Search from './../Search';
import DisplaySearchBar from '../../../layout/DisplaySearchBar/DisplaySearchBar';

import {
  SET_LOADING,
  SET_ALERT,

} from '../../../../actions/types';

const mockStore = configureStore([thunk]);
const initialState = {
  restaurants: { restaurants: ['foo'], alert: null },
};
const store = mockStore(initialState);
const mockSetAlert = jest.fn();
const mockGetRestaurants = jest.fn();

const wrapper = mount(
  <Provider store={store}>
    <Search setAlert={mockSetAlert} getRestaurants={mockGetRestaurants} />
  </Provider>
);

describe('Search', () => {
  afterEach(() => {
    jest.clearAllMocks();
  });

  test('1 - renders without errors', () => {
    expect(wrapper.find(DisplaySearchBar)).toHaveLength(1);
  });

  test('2 - if restaurants clearButton is rendered', () => {
    expect(wrapper.find('[data-test="clear"]')).toBeTruthy();
  });

  test('3 - setAlert called if search button is pressed with no input', () => {
    wrapper.find('form').simulate('submit', { preventDefault: () => {} });
    const actions= store.getActions();
    const expected={
      type: SET_ALERT,
      payload: expect.objectContaining({msg:"Please fill all the inputs"})
    };
    expect(actions[0]).toMatchObject(expected);
  });

  test('4 - getRestaurant called when inputs filled and search button clicked ', () => {
    wrapper
      .find('[name="where"]')
      .at(0)
      .simulate('change', { target: { value: 'foo', name:"where" } });

    wrapper
      .find('[name="what"]')
      .at(0)
      .simulate('change', { target: { value: 'foo',name:"what" } });

    wrapper
      .find('[data-test="best_match"]')
      .at(0)
      .simulate('click');

    wrapper.find('form').simulate('submit', { preventDefault: () => {} });
    const actions= store.getActions();
    const expected={
      type: SET_LOADING,
    };
    expect(actions).toContainEqual(expected);
     });
});
从“React”导入React;
从“酶”导入{mount};
从“redux模拟存储”导入configureStore;
从'react redux'导入{Provider};
从“redux thunk”导入thunk;
从“/../Search”导入搜索;
从“../../../layout/DisplaySearchBar/DisplaySearchBar”导入DisplaySearchBar;
进口{
设置_加载,
设置警报,
}来自“../../../../actions/types”;
const mockStore=configureStore([thunk]);
常量初始状态={
餐厅:{餐厅:['foo'],警报:null}
const mockSetAlert = jest.fn();
const mockGetRestaurants = jest.fn();

const wrapper = mount(
    <Search setAlert={mockSetAlert} getRestaurants={mockGetRestaurants} />
);

...

expect(mockSetAlert).toHaveBeenCalled();
expect(mockGetRestaurants).toHaveBeenCalled();
import React from 'react';
import { mount } from 'enzyme';
import configureStore from 'redux-mock-store';
import { Provider } from 'react-redux';
import thunk from 'redux-thunk';

import Search from './../Search';
import DisplaySearchBar from '../../../layout/DisplaySearchBar/DisplaySearchBar';

import {
  SET_LOADING,
  SET_ALERT,

} from '../../../../actions/types';

const mockStore = configureStore([thunk]);
const initialState = {
  restaurants: { restaurants: ['foo'], alert: null },
};
const store = mockStore(initialState);
const mockSetAlert = jest.fn();
const mockGetRestaurants = jest.fn();

const wrapper = mount(
  <Provider store={store}>
    <Search setAlert={mockSetAlert} getRestaurants={mockGetRestaurants} />
  </Provider>
);

describe('Search', () => {
  afterEach(() => {
    jest.clearAllMocks();
  });

  test('1 - renders without errors', () => {
    expect(wrapper.find(DisplaySearchBar)).toHaveLength(1);
  });

  test('2 - if restaurants clearButton is rendered', () => {
    expect(wrapper.find('[data-test="clear"]')).toBeTruthy();
  });

  test('3 - setAlert called if search button is pressed with no input', () => {
    wrapper.find('form').simulate('submit', { preventDefault: () => {} });
    const actions= store.getActions();
    const expected={
      type: SET_ALERT,
      payload: expect.objectContaining({msg:"Please fill all the inputs"})
    };
    expect(actions[0]).toMatchObject(expected);
  });

  test('4 - getRestaurant called when inputs filled and search button clicked ', () => {
    wrapper
      .find('[name="where"]')
      .at(0)
      .simulate('change', { target: { value: 'foo', name:"where" } });

    wrapper
      .find('[name="what"]')
      .at(0)
      .simulate('change', { target: { value: 'foo',name:"what" } });

    wrapper
      .find('[data-test="best_match"]')
      .at(0)
      .simulate('click');

    wrapper.find('form').simulate('submit', { preventDefault: () => {} });
    const actions= store.getActions();
    const expected={
      type: SET_LOADING,
    };
    expect(actions).toContainEqual(expected);
     });
});
  // previous code
export const Search = ({ getRestaurants, setAlert }) => {
   // rest of the code
import React from 'react';
import { mount } from 'enzyme';
import configureStore from 'redux-mock-store';
import { Provider } from 'react-redux';
import thunk from 'redux-thunk';

import { Search as BaseSearch } from './../Search';
import { DisplaySearchBar as BaseDisplaySearchBar } from '../../../layout/DisplaySearchBar/DisplaySearchBar';

const mockStore = configureStore([thunk]);
const initialState = {
  restaurants: { restaurants: ['foo'], alert: null },
};

const getRestaurants = jest.fn();
const setAlert = jest.fn();

let wrapper, store;

describe('Search', () => {
  beforeEach(() => {
    store = mockStore(initialState);

    wrapper = mount(
      <Provider store={store}>
        <BaseSearch setAlert={setAlert} getRestaurants={getRestaurants} />
      </Provider>
    );
  });

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

  test('1 - renders without errors', () => {
    expect(wrapper.find(BaseDisplaySearchBar)).toHaveLength(1);
  });

  test('2 - if restaurants clearButton is rendered', () => {
    expect(wrapper.find('[data-test="clear"]')).toBeTruthy();
  });

  test('3 - setAlert called if search button is pressed with no input', () => {
    wrapper.find('form').simulate('submit', { preventDefault: () => {} });

    expect(setAlert).toHaveBeenCalled();
  });

  test('4 - getRestaurants called when inputs filled and search button clicked ', () => {
    wrapper
      .find('[name="where"]')
      .at(0)
      .simulate('change', { target: { value: 'foo', name: 'where' } });

    wrapper
      .find('[name="what"]')
      .at(0)
      .simulate('change', { target: { value: 'foo', name: 'what' } });

    wrapper
      .find('[data-test="best_match"]')
      .at(0)
      .simulate('click');

    wrapper.find('form').simulate('submit', { preventDefault: () => {} });

    expect(getRestaurants).toHaveBeenCalled();
  });
});