Bagaimana cara menguji callback async XMLHttpRequest di Jest?
Saya memiliki tes Jest berikut yang mencakup memanggil server tiruan menggunakan ajax dengan XMLHttpRequest:
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();
});
});
yang jelas akan gagal karena panggilan AJAX ditangani secara asynchronous dan ekspektasi dibuat sebelum callback dipanggil.
Adakah cara di mana Jest bisa menunggu sampai panggilan balik dipanggil sebelum membuat ekspektasi?
Harap diperhatikan bahwa saya tidak dapat mengubah permintaan menjadi sinkron karena persyaratan domain. Dan saya juga tidak bisa mengubahnya menjadi API berbasis Janji hanya untuk bisa mengujinya. Ini hanyalah versi sederhana dari tes yang sedang ditulis sehingga orang-orang di sini dapat memahaminya dengan mudah. Kode sebenarnya berbeda dan memiliki abstraksi terhadap apa yang tertulis di sini.
Jawaban
Saya menyelesaikannya dengan pada dasarnya menggunakan async/awaitdukungan yang disediakan Jest. Solusinya adalah menggabungkan permintaan asinkron menjadi a Promisedan menyelesaikan Promisekapan onreadystatechangecallback dipanggil. Dengan demikian:
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();
});
});
Menggunakan awaitakan membuat tes berhenti sampai masalah Promiseterselesaikan. Saya tahu ini terasa agak hacky. Tapi hanya itu yang kami miliki untuk saat ini. Saya mencoba mencari solusi lain tetapi tidak dapat menemukannya.
Untuk mengetahui lebih banyak tentang menggunakan async/awaitdengan Jest, lihat di sini .
Anda dapat menggunakan Sinon untuk meniru XMLHttpRequests.
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' });
});
});
Lihat informasi lebih lanjut dari https://sinonjs.org/releases/v9.2.0/fake-xhr-and-server/
Cara lain adalah menggunakan setTimeout jika xhr-mock digunakan. Gabungkan pernyataan callback ke setTimeout dan panggil callback done () untuk pengujian Jest. Terasa seperti hack, tapi berhasil.
rest of your code
...
setTimeout(() => {
expect(doneCallback).toHaveBeenCalled();
done();
});