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