We are facing an unexpected behavior while testing async code with Jasmine. As far as we know, when you are using the done
function, expectations are not called until done is executed. But, that's not happening because the second expectation is failing, hence the $ctrl.todos
assignment never happened
Not working test
it('initializes the data when $onIinit', (done) => {
const expected = 'some result';
const response = Promise.resolve(expected);
spyOn(myService, 'getAll').and.returnValue(response);
// This method calls myService.getAll
$ctrl.$onInit();
expect(myService.getAll).toHaveBeenCalled();
expect($ctrl.todos).toEqual(false);
response.then(done);
});
Output: Expected undefined to equal false
On the other hand, this is working:
it('initializes the data when $onIinit', (done) => {
const expected = 'some result';
const response = Promise.resolve(expected);
spyOn(myService, 'getAll').and.returnValue(response);
// This method calls myService.getAll
$ctrl.$onInit();
expect(myService.getAll).toHaveBeenCalled();
response
.then(() => expect($ctrl.todos).toBe(expected))
.then(done);
});
Output: test pass
Controller method:
$ctrl.$onInit = () => {
myService.getAll().then((data) => {
$ctrl.todos = data;
});
};
I found a solution. I don't really need another then
. Specs with done
will not complete until it's done is called. Also, it should be always placed after expectations.
Working code:
it('initializes the data when $onIinit', (done) => {
const expected = 'some result';
const response = Promise.resolve(expected);
spyOn(myService, 'getAll').and.returnValue(response);
$ctrl.$onInit();
expect(myService.getAll).toHaveBeenCalled();
response
.then(() => {
expect($ctrl.todos).toBe(expected);
done();
});
});
Your aproach seems to be correct, and probably calling done
within afterEach
will make it works.
afterEach(function(done) {
done();
}, 1000);
But, I would recommend using $httpBackend, the fake HTTP backend implementation suitable for unit testing applications that use the $http service. We are using angularjs
anyway, right? So, why not take advantage of?
With $httpBackend we can make the requests, then response with mock data without really sending the request to a real server. Here an ilustrative example
it('initializes the data when $onIinit', () => {
const mockData = { data: { expected: 'some result' } };
spyOn(myService, 'getAll').and.callThrough();
$httpBackend.expect('POST', '/my-service/url').respond(200, mockData);
// This method calls myService.getAll
$ctrl.$onInit();
//flush pending request
$httpBackend.flush();
expect(myService.getAll).toHaveBeenCalled();
expect($ctrl.todos).toBeDefined();
});
Some explanations, $httpBackend.expect('POST', '/my-service/url')
, here note that 'POST'
need to match the method
used by your service in myService.getAll
, and '/my-service/url'
is the url
also used by your service in myService.getAll
.
It is required to call $httpBackend.flush();
, it will release all pending requests.
You will need to inject $httpBackend
into your tests, an easy way would be
describe('$httpBackend service in module ngMock', () => {
let $httpBackend;
beforeEach(inject(['$httpBackend', (_$httpBackend) => {
$httpBackend = _$httpBackend;
}]));
it('$httpBackend is defined', () => {
// here we can use $httpBackend
expect($httpBackend).toBeDefined();
});
});
Also, note that $httpBackend
is part of the ngMock module.
More info about testing angularjs code here
Hope it helps
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With