Como testar callbacks XMLHttpRequest assíncronos no Jest?
Eu tenho o seguinte teste Jest, que inclui chamar um servidor simulado usando ajax com 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();
});
});
que obviamente falhará porque a chamada AJAX é tratada de forma assíncrona e a expectativa é feita antes que o retorno de chamada seja chamado.
Existe alguma maneira em que Jest possa esperar até que o retorno de chamada seja chamado antes de fazer a expectativa?
Observe que não posso transformar a solicitação em uma solicitação síncrona devido aos requisitos de domínio. E também não posso transformá-lo em uma API baseada em Promise apenas para ser capaz de testá-lo. Esta é apenas uma versão simplificada do teste que está sendo escrito para que o pessoal aqui possa compreendê-lo facilmente. O código real é diferente e tem abstrações do que está escrito aqui.
Respostas
Resolvi basicamente usando o async/awaitsuporte que Jest oferece. A solução é envolver a solicitação assíncrona em um Promisee resolver o Promisequando o onreadystatechangeretorno de chamada for chamado. Assim sendo:
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();
});
});
Usar awaitfará o teste pausar até que o problema Promiseseja resolvido. Eu sei que isso parece um pouco estranho. Mas isso é tudo que temos por agora. Tentei procurar outras soluções, mas não consegui encontrar nenhuma.
Para saber mais sobre como usar async/awaitcom Jest, consulte aqui .
Você pode usar o Sinon para simular 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' });
});
});
Veja mais informações de https://sinonjs.org/releases/v9.2.0/fake-xhr-and-server/
Outra maneira seria usar setTimeout se xhr-mock for usado. Envolva a asserção de retorno de chamada para setTimeout e chame o retorno de chamada done () para o teste de Jest. Parece uma espécie de hack, mas funciona.
rest of your code
...
setTimeout(() => {
expect(doneCallback).toHaveBeenCalled();
done();
});