Warning: file_get_contents(/data/phpspider/zhask/data//catemap/3/reactjs/21.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Reactjs 如何正确监视react组件';是通过类原型还是酶包装器实例?_Reactjs_Typescript_Jestjs_Material Ui_Enzyme - Fatal编程技术网

Reactjs 如何正确监视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.

我试图断言,当我使用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.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而不是原始函数。