如何在Jest中测试异步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。这只是正在编写的测试的简化版本,这样这里的人就可以很容易地掌握它。实际的代码是不同的,它对这里所写的内容有抽象。

您可以使用Sinon模拟XMLHttpRequest。

import { createFakeServer } from 'sinon';
describe('Example', () => {
let xhr;
beforeEach(() => xhr = createFakeServer());
afterEach(() => xhr.restore());
test('example calls callback', () => {
jest.spyOn(exampleObject, 'exampleCallback');
xhr.respondWith('POST', '/expected/url', [200, { }, JSON.stringify({ foo: 'response' })]);
exampleObject.funcToDoRequest();
xhr.respond();

expect(exampleObject.exampleCallback).toBeCalledWith({ foo: 'response' });
});
});

查看更多信息https://sinonjs.org/releases/v9.2.0/fake-xhr-and-server/

如果使用xhr-mock,另一种方法是使用setTimeout。将回调断言包装为setTimeout,并为Jest测试调用done((回调。感觉有点像黑客,但有效。

rest of your code
...
setTimeout(() => {
expect(doneCallback).toHaveBeenCalled();
done();
});

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

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一起使用的更多信息,请参阅此处。

最新更新