Reputation: 1322
I'm trying to test a spy that would be called in the .then
block of a promise, but the done
in the then
block doesn't seem to be executed at all.
I'm getting timeout of 2000ms exceeded.
Here's what I'm testing (async):
/**
* Passed down to the LoginForm component to
* handle form submission.
*/
_submitHandler(data) {
return function(evt) {
evt.preventDefault && evt.preventDefault();
evt.stopPropagation && evt.stopPropagation();
return request('post', 'auth', data)
.then((res) => {
AuthActions.login();
return res;
})
}
}
Here's my test:
describe('when it succeeds', () => {
it('should login', (done) => {
sinon.spy(AuthActions, 'login');
Instance._submitHandler({})({})
.then((res) => {
console.log('Called!!!');
expect(AuthActions.login.called).to.equal(true);
AuthActions.login.restore();
done();
}, done);
});
});
I'm using Karma to run my tests; Chai and Sinon.
Upvotes: 2
Views: 632
Reputation: 4450
I also had this issue, the cause was trying to respond to the XHR before the connection was opened, e.g.
This code will throw INVALID_STATE_ERR - 0
from FakeXMLHttpRequest.setResponseHeaders
:
describe("get", function () {
beforeEach(function () {
this.xhr = sinon.useFakeXMLHttpRequest();
this.xhr.onCreate = function (request) {
request.respond(200, null, "");
};
});
afterEach(function () {
this.xhr.restore();
});
it("should make a request", function () {
myObject.get("testurl");
});
});
This code works:
describe("get", function () {
beforeEach(function () {
this.xhr = sinon.useFakeXMLHttpRequest();
this.requests = [];
this.xhr.onCreate = function (request) {
this.requests.push(request);
};
});
afterEach(function () {
this.xhr.restore();
});
it("should make a request", function () {
myObject.get("testurl");
this.requests[0].respond(200, null, "");
});
});
Reading the documentation, which itself does show the same technique of pushing to a requests array, I had subconsciously and inaccurately came away with the impression that onCreate, despite its name, was more like "on request".
myObject.get = function (url) {
var http = new XMLHttpRequest();
// the XHR instance exists
// readyState is 0
// onCreate runs now
http.open("GET", url);
http.send();
// readyState is 1
// can call setResponseHeaders and respond
}
The result is that you have to put your respond code after you call the method that runs XMLHttpRequest
's send
, as:
myObject.get("testurl");
this.requests[0].respond(200, null, "");
I was using the respond
method, but the same is true for setResponseHeaders
(respond
calls setResponseHeaders
)--in your test it was called too early.
Upvotes: 2
Reputation: 1322
I've finally solved this issue after hours. It looks like the then
block wasn't being called because an exception was being thrown due to xhr
.
Let me elaborate. I was using sinon's FakeXMLHttpRequest
, like so:
var requests, xhr;
beforeEach(() => {
requests = [];
xhr = sinon.useFakeXMLHttpRequest();
xhr.onCreate = (req) => {
req.setResponseHeaders({ /* */ });
requests.push(req);
}
});
By putting a console.log
on the catch
block, I found out that I was getting the error was INVALID_STATE_ERR EXCEPTION 0
. This leads me to the conclusion that xhr
was the problem all along.
And then I found out about sinon's fakeServer
, and used it instead (but I don't think this is actually the solution to this problem). Not really related, but I also used sandbox
here because saved me from writing countless of .restore
for stubs, etc.
describe('when it succeeds', () => {
var sandbox, server, Instance;
beforeEach(() => {
sandbox = sinon.sandbox.create();
sandbox.useFakeServer();
server = sandbox.server;
server.respondWith([200, { "Content-Type": "application/json" }, JSON.stringify({ data: { token: '' }})]);
sandbox.spy(AuthActions, 'login');
Instance = TestUtils.renderIntoDocument(<Component />);
});
afterEach(() => {
sandbox.restore();
});
it('should login', (done) => {
Instance._submitHandler({})({})
.then(() => {
expect(AuthActions.login.called).to.equal(true);
done();
}, done);
setTimeout(() => {
server.respond();
}, 0);
});
});
Upvotes: 1