为什么我的测试在我的(酶模拟事件)同步事件处理程序之前完成?

Ash*_*ons 1 javascript mocha.js jsdom reactjs enzyme

我有一个基于 mocha 的测试,它在我的 React 组件的基于 jsdom 的酶测试中在 onChange 处理程序之前完成,尽管该处理程序是使用 babel+ES2017 同步的。如果我用1 毫秒setTimeout()的时间来拨入电话;测试通过expect()

只是想知道故障在哪里?我确信这里有一些我没有考虑的简单概念。我在想 jsdom 或酶不会等待事件处理程序完成?fetch()一个问题因模拟所花费的时间长度而变得更加复杂fetch-mock(因为它通常是异步的)。

是否可以在没有setTimeout()sinon或 的情况下解决lolex,如果不能;可以用simon/lolex吗?

明天我希望我会重构它以避免在测试中模拟 fetch() 。

测试输出

</div>
    1) flashes a nice message upon success
Success now!!
End of function now. 


10 passing (4s)
1 failing

 1) <Signup /> flashes a nice message upon success:
 Uncaught AssertionError: expected { Object (root, unrendered, ...) } to have a length of 1 but got 0
  at test/integration/jsx/components/signup.test.js:38:54
  at _combinedTickCallback (internal/process/next_tick.js:67:7)
  at process._tickDomainCallback (internal/process/next_tick.js:122:9)
Run Code Online (Sandbox Code Playgroud)

引导程序

require('babel-register')();
require('babel-polyfill');
Run Code Online (Sandbox Code Playgroud)

...

var jsdom = require('jsdom').jsdom;
var exposedProperties = ['window', 'navigator', 'document'];

global.document = jsdom('');
global.window = document.defaultView;
global.FormData = document.defaultView.FormData;
Object.keys(document.defaultView).forEach((property) => {
  if (typeof global[property] === 'undefined') {
    exposedProperties.push(property);
    global[property] = document.defaultView[property];
  }
});

global.navigator = {
  userAgent: 'node.js'
};

documentRef = document;
Run Code Online (Sandbox Code Playgroud)

测试

import React from 'react';
import { expect } from 'chai';
import { shallow, mount, render } from 'enzyme';
import Signup from '../../../../assets/js/components/signup.jsx';
import fetchMock from 'fetch-mock';
import sinon from 'sinon';
import 'isomorphic-fetch';
Run Code Online (Sandbox Code Playgroud)

...

it("flashes a nice message upon success", function(){
  fetchMock.mock("*", {body: {}});
  const wrapper = shallow(<Signup />);

  wrapper.find('#email').simulate('change', {target: {id: 'email', value: validUser.email}});

  const signupEvent = {preventDefault: sinon.spy()};

  wrapper.find('#signupForm').simulate('submit', signupEvent);
  wrapper.update();

  console.log(wrapper.debug());
  expect(signupEvent.preventDefault.calledOnce).to.be.true;
  expect(wrapper.find('.alert-success')).to.have.length(1);
  expect(wrapper.find('.alert-success').text()).to.contain('Your sign up was successful!');

  fetchMock.restore();
});
Run Code Online (Sandbox Code Playgroud)

成分

async handleSubmit(e) {
  e.preventDefault();
  this.setState({ type: 'info', message: 'Sending...', shouldValidateForm: true });
  let form = new FormData(this.form);
  let response;
  let responseJson = {};
  try {
    response = await fetch("/signup", {
      method: "POST",
      body: form
    });
    responseJson = await response.json();
    if(!response.ok){
      throw new Error("There was a non networking error. ");
    }
    this.setState({ type: 'success', message: 'Your sign up was successful!' });
    console.log("Success now!!");
  } catch(err) {
    this.setState({ type: 'danger', message: "There was a technical problem. "});
  }
  console.log("End of function now. ");
}
Run Code Online (Sandbox Code Playgroud)

...

<form method="POST" onSubmit={this.handleSubmit} ref={(form) => {this.form = form;} } id="signupForm">
Run Code Online (Sandbox Code Playgroud)

tri*_*cot 5

我的第一个答案集中在 的异步性质上simulate,但从评论中可以清楚地看出,酶的该方法的实现不是异步的,因为它只是同步调用单击处理程序。所以这是我的答案的重写,重点关注异步行为的其他原因。

本次测试:

expect(wrapper.find('.alert-success')).to.have.length(1);
Run Code Online (Sandbox Code Playgroud)

...失败是因为当时以下行尚未执行:

this.setState({ type: 'success', message: 'Your sign up was successful!' });
Run Code Online (Sandbox Code Playgroud)

我在这里假设此setState调用会将alert-success类添加到消息元素中。

要了解为什么尚未设置此状态,请考虑执行流程:

wrapper.find('#signupForm').simulate('submit', signupEvent);
Run Code Online (Sandbox Code Playgroud)

这将触发onsubmit表单属性中指定的内容:

onSubmit={this.handleSubmit} 
Run Code Online (Sandbox Code Playgroud)

handleSubmit称。然后设置一个状态:

this.setState({ type: 'info', message: 'Sending...', shouldValidateForm: true });
Run Code Online (Sandbox Code Playgroud)

...但这不是您需要的状态:它不添加类alert-success。然后进行 Ajax 调用:

response = await fetch("/signup", {
    method: "POST",
    body: form
});
Run Code Online (Sandbox Code Playgroud)

fetch返回一个承诺,并将await暂停函数的执行,直到该承诺得到解决。同时,继续执行调用handleSubmit要执行的任何代码。在这种情况下,这意味着您的测试将继续,并最终执行:

expect(wrapper.find('.alert-success')).to.have.length(1);
Run Code Online (Sandbox Code Playgroud)

...失败了。表明挂起的 Ajax 请求有响应的事件可能已到达事件队列,但只有在当前执行的代码完成后才会对其进行处理。因此,在测试失败,由 返回的 Promisefetch得到解决。这是因为 的fetch内部实现有一个回调通知响应已到达,因此它解决了承诺。这使得函数handleSubmit“醒来”,因为await现在解除了执行的阻塞。

第二步await用于获取 JSON,这将再次引入事件队列循环。最终(不是双关语),代码将恢复并执行测试正在寻找的状态:

this.setState({ type: 'success', message: 'Your sign up was successful!' });
Run Code Online (Sandbox Code Playgroud)

因此...为了使测试成功,必须实现一个异步回调,该回调等待足够长的时间以便 Ajax 调用获得响应。

这可以通过 来完成setTimeout(done, ms),其中ms应该是足以确保 Ajax 响应可用的毫秒数。