可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
I've looked at various suggestions to solve testing a class property with no success and was wondering if anyone could possibly cast a little more light on where I may be going wrong, here are the tests I've tried all with the error Expected mock function to have been called, but it was not called.
Search.jsx
import React, { Component } from 'react' import { func } from 'prop-types' import Input from './Input' import Button from './Button' class SearchForm extends Component { static propTypes = { toggleAlert: func.isRequired } constructor() { super() this.state = { searchTerm: '' } this.handleSubmit = this.handleSubmit.bind(this) } handleSubmit = () => { const { searchTerm } = this.state const { toggleAlert } = this.props if (searchTerm === 'mocky') { toggleAlert({ alertType: 'success', alertMessage: 'Success!!!' }) this.setState({ searchTerm: '' }) } else { toggleAlert({ alertType: 'error', alertMessage: 'Error!!!' }) } } handleChange = ({ target: { value } }) => { this.setState({ searchTerm: value }) } render() { const { searchTerm } = this.state const btnDisabled = (searchTerm.length === 0) === true return ( <div className="well search-form soft push--bottom"> <ul className="form-fields list-inline"> <li className="flush"> <Input id="search" name="search" type="text" placeholder="Enter a search term..." className="text-input" value={searchTerm} onChange={this.handleChange} /> <div className="feedback push-half--right" /> </li> <li className="push-half--left"> <Button className="btn btn--positive" disabled={btnDisabled} onClick={this.handleSubmit}> Search </Button> </li> </ul> </div> ) } } export default SearchForm
First option:
it('should call handleSubmit function on submit', () => { const wrapper = shallow(<Search toggleAlert={jest.fn()} />) const spy = jest.spyOn(wrapper.instance(), 'handleSubmit') wrapper.instance().forceUpdate() wrapper.find('.btn').simulate('click') expect(spy).toHaveBeenCalled() spy.mockClear() })
Second option:
it('should call handleSubmit function on submit', () => { const wrapper = shallow(<Search toggleAlert={jest.fn()} />) wrapper.instance().handleSubmit = jest.fn() wrapper.update() wrapper.find('.btn').simulate('click') expect(wrapper.instance().handleSubmit).toHaveBeenCalled() })
I get that with a class property the function is an instance of the class requiring the component to be updated in order to register the function, it looks however like the component handleSubmit function gets called instead of the mock?
Swapping out handleSubmit to be a class function as a method gives me access on the class prototype which passes the test when spying on Search.prototype but I'd really like to get a solution to the class property approach.
All suggestions and recommendations would be grateful!
回答1:
I suppose your unit test should be robust enough to catch the error
, if case of any undesirable code changes.
Please include strict assertions in your tests.
For the conditional statements, please cover the branches as well. E.g in case of if
and else
statement you will have to write two
tests.
For user actions, you should try to simulate the actions rather than calling the function manually.
Please see the example below,
import React from 'react'; import { shallow } from 'enzyme'; import { SearchForm } from 'components/Search'; describe('Search Component', () => { let wrapper; const toggleAlert = jest.fn(); const handleChange = jest.fn(); const successAlert = { alertType: 'success', alertMessage: 'Success!!!' } const errorAlert = { alertType: 'error', alertMessage: 'Error!!!' } beforeEach(() => { wrapper = shallow(<SearchForm toggleAlert={toggleAlert} />); }); it('"handleSubmit" to have been called with "mocky"', () => { expect(toggleAlert).not.toHaveBeenCalled(); expect(handleChange).not.toHaveBeenCalled(); wrapper.find('Input').simulate('change', { target: { value: 'mocky' } }); expect(handleChange).toHaveBeenCalledTimes(1); expect(wrapper.state().searchTerm).toBe('mocky'); wrapper.find('Button').simulate('click'); expect(toggleAlert).toHaveBeenCalledTimes(1); expect(toggleAlert).toHaveBeenCalledWith(successAlert); expect(wrapper.state().searchTerm).toBe(''); }); it('"handleSubmit" to have been called with "other than mocky"', () => { expect(toggleAlert).not.toHaveBeenCalled(); expect(handleChange).not.toHaveBeenCalled(); wrapper.find('Input').simulate('change', { target: { value: 'Hello' } }); expect(handleChange).toHaveBeenCalledTimes(1); expect(wrapper.state().searchTerm).toBe('Hello'); wrapper.find('Button').simulate('click'); expect(toggleAlert).toHaveBeenCalledTimes(1); expect(toggleAlert).toHaveBeenCalledWith(errorAlert); expect(wrapper.state().searchTerm).toBe('Hello'); }); });
回答2:
So I've managed to create a working solution by first of all updating the wrapper instance and then updating the wrapper. Test now works.
Working test looks like:
it('should call handleSubmit function on submit', () => { const wrapper = shallow(<Search toggleAlert={jest.fn()} />) wrapper.instance().handleSubmit = jest.fn() wrapper.instance().forceUpdate() wrapper.update() wrapper.find('.btn').simulate('click') expect(wrapper.instance().handleSubmit).toHaveBeenCalled() })
回答3:
Try something like this
it('should call handleSubmit function on submit', () => { const toggleAlert = jest.fn(); const wrapper = shallow(<Search toggleAlert={toggleAlert} />) wrapper.setState({ searchText: 'mocky' }); wrapper.find('Button').at(0).simulate('click'); expect(toggleAlert).toHaveBeenLastCalledWith({ alertType: 'success', alertMessage: 'Success!!!' }); })
****Update
constructor(props) { super(props) //you have to add props to access it this.props this.state = { searchTerm: '' } this.handleSubmit = this.handleSubmit.bind(this) }
回答4:
You shouldn't need to write unit tests for this scenario. You should be able to trust that the framework will fire the correct handlers that you've provided. A more useful test would be one which mocks the toggleAlert
prop and tests the instance method handleSubmit
. This is where the majority of custom logic will reside and consequently where we are most likely to find errors. Snapshot testing should be fine for anything that is part of the render function output.
A sensible test suite for this component would resemble something like the following:
describe('handleSubmit', () => { let wrapper; let spy; describe('when searchTerm is "mocky"', () => { beforeEach(() => { spy = jest.fn(); wrapper = shallow(<SearchForm toggleAlert={spy} />); wrapper.setState({ searchTerm: 'mocky' }); }); it('will fire spy with expected arguments', () => { // verify that spy has not been fired prior to test expect(spy).not.toBeCalled(); wrapper.instance().handleSubmit(); expect(spy).toBeCalled(); expect(spy).toBeCalledWith({ alertType: 'success', alertMessage: 'Success!!!' }); }); it('will set searchTerm to ""', () => { expect(wrapper.state('searchTerm')).toBe('mocky'); wrapper.instance().handleSubmit(); expect(wrapper.state('searchTerm')).toBe(''); }); }); describe('when searchTerm is "something else"', () => { beforeEach(() => { spy = jest.fn(); wrapper = shallow(<SearchForm toggleAlert={spy} />); wrapper.setState({ searchTerm: 'something else' }); }); it('will fire spy with expected arguments', () => { // verify that spy has not been fired prior to test expect(spy).not.toBeCalled(); wrapper.instance().handleSubmit(); expect(spy).toBeCalled(); expect(spy).toBeCalledWith({ alertType: 'error', alertMessage: 'Error!!!' }); }); }); });