Reactjs 如何正确监视react组件';是通过类原型还是酶包装器实例?
我试图断言,当我使用Jest和Ezyme模拟单击时,会调用React类组件方法。当我试图窥探类原型或Reactjs 如何正确监视react组件';是通过类原型还是酶包装器实例?,reactjs,typescript,jestjs,material-ui,enzyme,Reactjs,Typescript,Jestjs,Material Ui,Enzyme,我试图断言,当我使用Jest和Ezyme模拟单击时,会调用React类组件方法。当我试图窥探类原型或wrapper.instance()时,我得到错误:无法窥探searchBooks属性,因为它不是函数;而不是给定未定义的 我的相关依赖项: "devDependencies": { "enzyme": "^3.9.0" "enzyme-adapter-react-16": "^1.6.0", "jest": "^23.6.0", "jest-enzyme": "^7.0.
wrapper.instance()
时,我得到错误:无法窥探searchBooks属性,因为它不是函数;而不是给定未定义的
我的相关依赖项:
"devDependencies": {
"enzyme": "^3.9.0"
"enzyme-adapter-react-16": "^1.6.0",
"jest": "^23.6.0",
"jest-enzyme": "^7.0.1",
"ts-jest": "^23.10.3",
"typescript": "^3.1.1",
...
"dependencies": {
"@material-ui/core": "^3.2.0",
"@material-ui/icons": "^3.0.1",
"@material-ui/lab": "^3.0.0-alpha.23",
"react": "^16.5.2",
我已经尝试过这些选项,它们抛出了前面提到的错误
let spy = jest.spyOn(wrapper.instance() as MyComponentClass, 'methodName');
let spy2 = jest.spyOn(MyComponentClass.prototype, 'methodName');
我可以用下面的方法消除错误,但是间谍仍然没有被调用
let spy3 = jest.spyOn(wrapper.find(MyComponentClass).instance() as MyComponentClass, 'methodName');
下面是我的代码
import * as React from 'react';
import { Fragment, Component, ChangeEvent } from 'react';
import { AudioType } from '../../model/audio';
import withStyles, { WithStyles } from '@material-ui/core/styles/withStyles';
import Typography from '@material-ui/core/Typography';
import TextField from '@material-ui/core/TextField';
import BookSearchStyles from './BookSearchStyles';
import BookDetail from './BookDetail';
import SearchIcon from '@material-ui/icons/Search';
import IconButton from '@material-ui/core/IconButton';
import { VolumeInfo } from '../../model/volume';
export interface BookSearchProps extends WithStyles<typeof BookSearchStyles> {
search?: (query: string) => void;
}
export interface BookSearchState {
searchQuery?: string;
}
export class BookSearch extends Component<BookSearchProps, BookSearchState> {
state: BookSearchState = {
searchQuery: '',
};
handleChange = (event: ChangeEvent<HTMLInputElement>) => {
this.setState({
[event.target.name]: event.target.value,
});
};
searchBooks = () => {
if (this.state.searchQuery) {
this.props.search(this.state.searchQuery);
}
};
render() {
const { classes, volumes } = this.props;
return (
<Fragment>
<div className={classes.searchPrompt}>
<form className={classes.formContainer}>
<div className={classes.search}>
<TextField
id="query-text-field"
name="searchQuery"
label="enter book title or author"
className={classes.textField}
value={this.state.searchQuery}
onChange={this.handleChange}
fullWidth={true}
/>
{
<IconButton
onClick={this.searchBooks}
data-test="search-button"
>
<SearchIcon />
</IconButton>
}
</div>
</form>
</div>
</Fragment>
);
}
}
export default withStyles(BookSearchStyles)(BookSearch);
import*as React from'React';
从“react”导入{Fragment,Component,ChangeEvent};
从“../../model/audio”导入{AudioType};
导入withStyles,{withStyles}来自“@material ui/core/styles/withStyles”;
从“@material ui/core/Typography”导入排版;
从“@material ui/core/TextField”导入TextField;
从“./BookSearchStyles”导入BookSearchStyles;
从“/BookDetail”导入BookDetail;
从“@material ui/icons/Search”导入SearchIcon;
从“@material ui/core/IconButton”导入IconButton;
从“../../model/volume”导入{VolumeInfo};
导出接口BookSearchProps随样式扩展{
搜索?:(查询:字符串)=>作废;
}
导出接口BookSearchState{
searchQuery?:字符串;
}
导出类BookSearch扩展组件{
状态:BookSearchState={
searchQuery:“”,
};
handleChange=(事件:ChangeEvent)=>{
这是我的国家({
[event.target.name]:event.target.value,
});
};
searchBooks=()=>{
if(this.state.searchQuery){
this.props.search(this.state.searchQuery);
}
};
render(){
const{classes,volumes}=this.props;
返回(
{
}
);
}
}
导出默认样式(BookSearchStyles)(BookSearch);
我的测试
import * as React from 'react';
import { mount, ReactWrapper } from 'enzyme';
import BookSearchWrapped from './index';
import { BookSearch, BookSearchProps, BookSearchState } from './index';
describe("<BookSearch />", () => {
let wrapper: ReactWrapper<BookSearchProps, BookSearchState, BookSearch>;
let component: ReactWrapper<BookSearchProps, BookSearchState>;
let search: (query: string) => void;
beforeEach(() => {
search = jest.fn();
wrapper = mount(
<BookSearchWrapped
search={search}
/>
);
component = wrapper.find(BookSearch);
});
it('renders successfully', () => {
expect(wrapper.exists()).toBe(true);
});
it("doesn't call props.search() function when the query string is empty", () => {
let spy = jest.spyOn(wrapper.instance() as BookSearch, 'searchBooks'); //THROWS ERROR
let spy2 = jest.spyOn(BookSearch.prototype, 'searchBooks'); //THROWS ERROR
let spy3 = jest.spyOn(component.instance() as BookSearch, 'searchBooks'); //NOT CALLED
wrapper.find(`IconButton[data-test="search-button"]`).simulate('click');
expect(spy).toHaveBeenCalled();
expect(spy2).toHaveBeenCalled();
expect(spy3).toHaveBeenCalled();
expect(search).not.toHaveBeenCalled();
});
});
import*as React from'React';
从“酶”导入{mount,ReactWrapper};
从“./index”导入BookSearchWrapped;
从“/index”导入{BookSearch,BookSearchProps,booksearchtate};
描述(“,()=>{
让包装器:反应包装器;
let组件:ReactWrapper;
let search:(查询:字符串)=>void;
在每个之前(()=>{
search=jest.fn();
包装=装载(
);
component=wrapper.find(BookSearch);
});
它('渲染成功',()=>{
expect(wrapper.exists()).toBe(true);
});
它(“当查询字符串为空时不调用props.search()函数”,()=>{
让spy=jest.spyOn(wrapper.instance()作为BookSearch,'searchBooks');//抛出错误
让spy2=jest.spyOn(BookSearch.prototype,'searchBooks');//抛出错误
让spy3=jest.spyOn(component.instance()作为BookSearch,'searchBooks');//不调用
find(`IconButton[data test=“search button”]`)。simulate('click');
期望(间谍)。已被调用();
expect(spy2).tohavebeincalled();
期望(间谍3).已被调用();
expect(search).not.tohavebeencall();
});
});
理想情况下,我应该能够做类似的事情。您可以尝试使用jest函数而不是spy函数
const instance = wrapper.instance() as BookSearch
instance.searchBooks = jest.fn()
wrapper.find(`IconButton[data-test="search-button"]`).simulate('click');
expect(instance.searchBooks).toHaveBeenCalled()
searchBooks
是一个实例属性
…因此它在实例存在之前不存在,您只能使用实例对其进行监视
…但它也直接绑定到onClick
…这意味着在spy中包装searchBooks
不会有任何效果,直到组件重新呈现
因此,修复它的两个选项是使用箭头函数调用searchBooks
,或者在测试onClick
之前重新呈现组件,使其绑定到spy而不是原始函数
下面是一个简单的示例,演示了两种方法:
import * as React from 'react';
import { shallow } from 'enzyme';
class MyComponent extends React.Component {
func1 = () => { } // <= instance property
func2 = () => { } // <= instance property
render() { return (
<div>
<button id='one' onClick={this.func1}>directly bound</button>
<button id='two' onClick={() => { this.func2() }}>arrow function</button>
</div>
); }
}
test('click', () => {
const wrapper = shallow<MyComponent>(<MyComponent/>);
const spy1 = jest.spyOn(wrapper.instance(), 'func1');
const spy2 = jest.spyOn(wrapper.instance(), 'func2');
wrapper.find('#one').simulate('click');
expect(spy1).not.toHaveBeenCalled(); // Success! (onClick NOT bound to spy)
wrapper.find('#two').simulate('click');
expect(spy2).toHaveBeenCalledTimes(1); // Success!
wrapper.setState({}); // <= force re-render (sometimes calling wrapper.update isn't enough)
wrapper.find('#one').simulate('click');
expect(spy1).toHaveBeenCalledTimes(1); // Success! (onClick IS bound to spy)
wrapper.find('#two').simulate('click');
expect(spy2).toHaveBeenCalledTimes(2); // Success!
});
import*as React from'React';
从“酶”导入{shall};
类MyComponent扩展了React.Component{
func1=()=>{}/{}//arrow函数
); }
}
测试('单击',()=>{
常量包装器=浅();
const spy1=jest.spyOn(wrapper.instance(),'func1');
const spy2=jest.spyOn(wrapper.instance(),'func2');
wrapper.find(“#one”).simulate('click');
expect(spy1).not.tohavebeencall();//成功!(onClick未绑定到spy)
wrapper.find(“#two”).simulate('click');
期待(spy2)。获得足够的时间(1);//成功!
wrapper.setState({});//谢谢你的回复。我不想用模拟来替换searchBooks
方法。在这个测试中,我希望searchBooks
方法像往常一样执行,因为我还想对searchBooks
实现中发生的事情做出断言。也就是说,search
函数如果searchQuery
状态为空,则不会调用ion prop。感谢您的详细解释。我测试了您的两个选项,它们都正常工作。在模拟onClick
之前,我选择使用wrapper.setState({})
重新呈现组件,因此它绑定到spy而不是原始函数。