如何在玩笑中模拟 window.alert 方法?

IT技术 reactjs jestjs enzyme
2021-05-17 03:27:58

我有以下 React 组件:

class Form extends React.Component {

   constructor(props) {
      super(props);
      this.state = this._createEmptyTodo();
   }

   render() {

      this.i18n = this.context;

      return (
         <div className="form">
            <form onSubmit={this._handleSubmit.bind(this)}>

               <input
                  placeholder={this.i18n.placeholders.addTitle}
                  type="text"
                  value={this.state.title}
                  onChange={this._handleTitleChange.bind(this)}></input>

               <textarea
                  placeholder={this.i18n.placeholders.addDescription}
                  value={this.state.description}
                  onChange={this._handleDescriptionChange.bind(this)}></textarea>

               <button>{this.i18n.buttons.submit}</button>
            </form>
         </div>
      );
   }

   _handleTitleChange(e) {
      this.setState({
         title: e.target.value
      });
   }

   _handleDescriptionChange(e) {
      this.setState({
         description: e.target.value
      });
   }

   _handleSubmit(e) {

      e.preventDefault();

      var todo = {
         date: new Date().getTime(),
         title: this.state.title.trim(),
         description: this.state.description.trim(),
         done: false
      };

      if (!todo.title) {
         alert(this.i18n.errors.title);
         return;
      }

      if (!todo.description) {
         alert(this.i18n.errors.description);
         return;
      }

      this.props.showSpinner();
      this.props.actions.addTodo(todo);
      this.setState(this._createEmptyTodo());
   }

   _createEmptyTodo() {
      return {
         "pkey": null,
         "title": "",
         "description": ""
      };
   }
}

以及相关测试:

const i18nContext = React.createContext();
Form.contextType = i18nContext;

    describe('The <Form> component', () => {

       var wrapper;
       var showSpinner;
       var actions = {}

       beforeEach(() => {
          showSpinner = jest.fn();
          actions.addTodo = jest.fn();
          wrapper = mount(<i18nContext.Provider value={i18n["en"]}>
             <Form
                showModalPanel={showSpinner}
                actions={actions} />
          </i18nContext.Provider>);
       });

       test("validate its input", () => {
          window.alert = jest.fn();
          wrapper.find("button").simulate("click");
          expect(window.alert.mock.calls.length).toBe(1);//<<< this FAILS!
       });
    });

这种形式,当按钮被点击时,它只是使用alert.

现在,当我运行测试时,我得到了这个:

expect(received).toBe(expected) // Object.is equality

Expected: 1
Received: 0

这是一个失败,因为模拟显然没有被调用。但是我向您保证,表单组件在单击其按钮时会发出一条消息。

我怀疑,由于某些原因,当使用酶以编程方式执行点击时,组件window.alert不会Form使用模拟。

任何人?

2个回答

在带有 JSDOM 的 Jest 配置中global.window === global,因此可以在window.

最好像这样嘲笑它

jest.spyOn(window, 'alert').mockImplementation(() => {});

因为window.alert = jest.fn()污染了这个套件中的其他测试。

黑盒测试的问题在于故障排除更难,而且依赖于真实 DOM 预期的行为可能会导致问题,因为 Enzyme 不需要支持这种行为。不知道实际问题是否handleSubmit被调用,alert没有调用模拟只是证明出了问题。

在这种情况click下,按钮上的事件不会导致submit父表单上的事件,因为 Enzyme不支持设计

一个正确的单元测试策略是为所有单元设置间谍或模拟,除了被测试的单元,这是提交事件处理程序。它通常涉及shallow而不是mount

应该是:

  jest.spyOn(window, 'alert').mockImplementation(() => {});
  const formWrapper = wrapper.find(Form).dive();
  jest.spyOn(formWrapper.instance(), '_handleSubmit');
  formWrapper.find("form").simulate("submit");
  expect(formWrapper.instance()._handleSubmit).toBeCalled();
  expect(window.alert).toBeCalledWith(...);

状态应该直接改变formWrapper.setState而不是 DOM 事件模拟。

一个更孤立的单元测试是断言form提供了预期的onSubmitprops并formWrapper.instance()._handleSubmit(...)直接调用

相反window,您可以使用global.

global.alert = jest.fn();

这是因为浏览器使用window名称,而 nodejs 使用global名称。