Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Testing async function with jasmine

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;
  });
};
like image 241
mattias Avatar asked Aug 01 '17 18:08

mattias


2 Answers

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();
  });
});
like image 179
mattias Avatar answered Sep 27 '22 22:09

mattias


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

like image 41
Castro Roy Avatar answered Sep 27 '22 21:09

Castro Roy