Come testare i callback XMLHttpRequest asincroni in Jest?
Ho il seguente test Jest che include la chiamata a un server fittizio utilizzando ajax con 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();
});
});
che ovviamente fallirà perché la chiamata AJAX viene gestita in modo asincrono e l'aspettativa viene fatta prima che venga chiamato il callback.
C'è un modo in cui Jest può aspettare che venga richiamata la richiamata prima di fare l'aspettativa?
Tieni presente che non posso trasformare la richiesta in una sincrona a causa dei requisiti del dominio. E nemmeno posso trasformarlo in un'API basata su Promise solo per poterlo testare. Questa è solo una versione semplificata del test che è stato scritto in modo che le persone qui possano capirlo facilmente. Il codice effettivo è diverso e presenta astrazioni rispetto a quanto scritto qui.
Risposte
L'ho risolto fondamentalmente utilizzando il async/awaitsupporto fornito da Jest. La soluzione è racchiudere la richiesta asincrona in un Promisee risolvere il Promisemomento in cui onreadystatechangeviene chiamato il callback. Come tale:
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();
});
});
L'utilizzo awaitrenderà il test in pausa fino a quando non Promiseviene risolto. So che sembra un po 'complicato. Ma per ora è tutto ciò che abbiamo. Ho provato a cercare altre soluzioni ma non sono riuscito a trovarne nessuna.
Per saperne di più sull'utilizzo async/awaitcon Jest, fare riferimento qui .
Puoi usare Sinon per simulare 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' });
});
});
Vedere ulteriori informazioni da https://sinonjs.org/releases/v9.2.0/fake-xhr-and-server/
Un altro modo sarebbe usare setTimeout se si usa xhr-mock. Racchiudi l'asserzione di callback su setTimeout e chiama la callback done () per il test Jest. Sembra una specie di hack, ma funziona.
rest of your code
...
setTimeout(() => {
expect(doneCallback).toHaveBeenCalled();
done();
});