如何在Jest中测试异步XMLHttpRequest回调?

时间:2020-10-04 14:09:06

标签: javascript ajax testing jestjs xmlhttprequest

我有以下Jest测试,包括使用带有XMLHttpRequest的ajax调用模拟服务器:

import mock from "xhr-mock";

describe("ajax callbacks", function() {
  beforeEach(function() {
    mock.setup();
  });

  afterAll(function() {
    mock.teardown();
  });

  it("gets called when done", function(done) {
    mock.get("/get-url", {
      status: 200,
      body: '{ "key": "value" }'
    });

    const doneCallback = jest.fn();

    const xhr = new XMLHttpRequest();
    xhr.open("GET", "/get-url");
    xhr.onreadystatechange = function() {
      if (xhr.readyState === XMLHttpRequest.DONE) {
        doneCallback();
        done();
      }
    }
    xhr.send();

    expect(doneCallback).toHaveBeenCalled();
  });
});

显然会失败,因为AJAX调用是异步处理的,并且期望是在调用回调之前做出的。

Jest有什么办法可以等到回调被调用才产生期望?

请注意,由于域要求,我无法将请求转换为同步请求。而且我也不能仅仅为了能够对其进行测试就将其转换为基于Promise的API。这只是正在编写的测试的简化版本,以便此处的人们可以轻松地掌握它。实际的代码是不同的,并且对此处编写的内容具有抽象性。

2 个答案:

答案 0 :(得分:1)

我基本上通过使用Jest提供的async/await支持来解决此问题。解决方案是将异步请求包装到Promise中,并在调用Promise回调时解析onreadystatechange。因此:

import mock from "xhr-mock";

describe("ajax callbacks", function() {
  beforeEach(function() {
    mock.setup();
  });

  afterAll(function() {
    mock.teardown();
  });

  it("gets called when done", async function() {
    mock.get("/get-url", {
      status: 200,
      body: '{ "key": "value" }'
    });

    const doneCallback = jest.fn();

    const xhr = new XMLHttpRequest();
    xhr.open("GET", "/get-url");

    await new Promise(function(resolve) {
      xhr.onreadystatechange = function() {
        if (xhr.readyState === XMLHttpRequest.DONE) {
          doneCallback();
          resolve();
        }
      }
      xhr.send();
    });

    expect(doneCallback).toHaveBeenCalled();
  });
});

使用await将使测试暂停,直到Promise得到解决。我知道这感觉有些棘手。但这就是我们目前所拥有的。我尝试研究其他解决方案,但找不到任何解决方案。

要了解有关将async/await与Jest一起使用的更多信息,请参考here

答案 1 :(得分:1)

您可以使用Sinon模拟XMLHttpRequests。

Text

查看来自https://sinonjs.org/releases/v9.2.0/fake-xhr-and-server/

的更多信息

如果使用xhr-mock,另一种方法是使用setTimeout。将回调断言包装到setTimeout并为Jest测试调用done()回调。感觉有点hack,但是可以。

refresh()