为什么我的测试(酶模拟事件)同步事件处理程序之前完成? [英] Why does my test finish before my (enzyme simulated event) synchronous event handler?

查看:87
本文介绍了为什么我的测试(酶模拟事件)同步事件处理程序之前完成?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一个基于摩卡的测试,该测试在我的React组件基于jsdom的酶测试中的onChange处理程序之前完成,尽管该处理程序是使用babel + ES2017同步的.如果我执行 1ms setTimeout()来放置我的expect()呼叫;测试通过.

I have a mocha based test which finishes before my onChange handler in a jsdom based enzyme test of my React component, despite that handler being synchronous using babel+ES2017. If I do a setTimeout() of 1ms to put my expect() calls in; the test passes.

只是想知道故障发生在哪里?我确定这里没有考虑一些简单的概念.我在想jsdom或ase不要等待事件处理程序完成?将fetch()fetch-mock进行模拟的时间长度加重了一个问题(因为它通常是异步的).

Just wondering where the break down is? I'm sure there is some simple concept here I'm not considering. I'm thinking jsdom or enzyme does not wait around for the event handler to finish? A problem compounded by the length of time mocking fetch() with fetch-mock is taking (because it is asynchronous normally).

在没有setTimeout()sinonlolex的情况下是否可以解析,如果不是,则可以解析; simon/lolex有可能吗?

Is it resolvable without setTimeout(), sinon or lolex, and if not; is it possible with simon / lolex?

明天我希望我将其重构以避免在测试中模拟fetch().

Tomorrow I expect I'll refactor it to avoid mocking fetch() in the tests.

测试输出

</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)

引导程序

require('babel-register')();
require('babel-polyfill');

...

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;

测试

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';

...

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();
});

组件

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. ");
}

...

<form method="POST" onSubmit={this.handleSubmit} ref={(form) => {this.form = form;} } id="signupForm">

推荐答案

我的第一个答案集中在simulate的异步特性上,但是从注释中可以清楚地看出该方法

My first answer focussed on the asynchronous nature of simulate, but from comments it became clear enzyme's implementation of that method is not asynchronous as it just calls the click handler synchronously. So this is a rewrite of my answer, focussing on the other causes for asynchronous behaviour.

该测试:

expect(wrapper.find('.alert-success')).to.have.length(1);

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

... fails because at that time the following line has not yet been executed:

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

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

I assume here that this setState call will add the alert-success class to the message element.

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

To see why this state has not yet been set, consider the execution flow:

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

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

This will trigger what is specified in the onsubmit attribute of the form:

onSubmit={this.handleSubmit} 

因此调用了handleSubmit.然后设置状态:

So handleSubmit is called. Then a state is set:

this.setState({ type: 'info', message: 'Sending...', shouldValidateForm: true });

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

... but this is not the state you need: it does not add the alert-success class. Then an Ajax call is made:

response = await fetch("/signup", {
    method: "POST",
    body: form
});

fetch返回一个承诺,并且await将暂停该函数的执行,直到该承诺被解决为止.同时,在对的调用之后,将继续执行将要执行的所有代码.在这种情况下,这意味着您的测试将继续并最终执行:

fetch returns a promise, and await will pause the execution of the function until that promise is resolved. In the mean time, execution continues with any code that is to be executed following the call to handleSubmit. In this case that means your test continues, and eventually executes:

expect(wrapper.find('.alert-success')).to.have.length(1);

...失败.表示未决的Ajax请求有响应的事件可能已到达事件队列,但是只有在当前执行的代码完成后才进行处理.因此,在 测试失败之后,由fetch返回的诺言就得到了解决.这是因为fetch的内部实现具有一个回调,用于通知响应已到达,从而解决了诺言.这将使功能handleSubmit唤醒",因为await现在取消了执行.

...which fails. The event signalling that the pending Ajax request has a response might have arrived on the event queue, but it will only be processed after the currently executing code has finished. So after the test has failed, the promise, that was returned by fetch, gets resolved. This is because the fetch's internal implementation has a callback notifying that the response has arrived, and thus it resolves the promise. This makes the function handleSubmit "wake up", as the await now unblocks the execution.

还有第二个await用于获取JSON,这将再次引入事件队列周期.最终(非双关语),代码将恢复并执行测试所寻找的状态:

There is a second await for getting the JSON, which again will introduce a event queue cycle. Eventually (pun not intended), the code will resume and execute the state the test was looking for:

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

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

So... for the test to succeed, it must have an asynchronous callback implemented that waits long enough for the Ajax call to get a response.

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

This can be done with setTimeout(done, ms), where ms should be a number of milliseconds that is great enough to ensure the Ajax response has become available.

这篇关于为什么我的测试(酶模拟事件)同步事件处理程序之前完成?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆