Testing an asynchronous function with jasmine

We are experiencing unexpected behavior while testing asynchronous code with Jasmine. As far as we know, when you use a function done

, expectations are not called until the execution is done. But this doesn't happen because the second expectation fails, so the assignment $ctrl.todos

never happened

Test not 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();
  expect($ctrl.todos).toEqual(false);

  response.then(done);
});

      

Output: Expected undefined equals false

On the other hand, this works:

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;
  });
};

      

+3


source to share


3 answers


I found a solution. I don't need another one then

. The specifications with done

will not be completed until this is called. Moreover, it should always be 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();
  });
});

      

+1


source


Your aproach seems to be correct and probably a call done

inside afterEach

will make it work.

afterEach(function(done) {
  done();
}, 1000);

      

But I would recommend using $ httpBackend , a fake HTTP backend support suitable for unit testing applications that use the $ http service . We use it anyway angularjs

, right? So why not take advantage of the benefits?

With $ httpBackend, we can execute requests and then respond with mock data without sending the request to the real server. Here's an illustrative 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();
});

      

In some clarification $httpBackend.expect('POST', '/my-service/url')

here, note that 'POST'

you need to map method

that is used by your service in myService.getAll

, and '/my-service/url'

- this url

is also used by your service in myService.getAll

.



Required to be called $httpBackend.flush();

, it will output all pending requests.

You will need to enter $httpBackend

in 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 it $httpBackend

is part of the ngMock module .

More info on testing angularjs code here

Hope it helps

0


source


Promise

located outside the Angular world - you need to wait for the result to be available in the next event queue. tick - dirty (almost dirty) hack usagesetTimeout

angular.module('test', [])
  .controller('underTest', function($scope, myService) {
    $scope.$onInit = function() {
      myService.getAll().then(function(data) {
        $scope.todos = data
      })
    }
  })

describe('Controller: somethingChanged', function() {
  var scope, myService

  beforeEach(function() {
    module('test')
  })

  beforeEach(function() {
    module(function($provide) {
      $provide.value('myService', {
        getAll: function() {}
      })
    })
  })

  beforeEach(inject(function($controller, _$rootScope_, _myService_) {
    myService = _myService_
    scope = _$rootScope_.$new()
    $controller('underTest', {
      $scope: scope
    })
  }))

  it('initializes the data when $onIinit', function(done) {
    const expected = 'some result'
    const response = Promise.resolve(expected)

    spyOn(myService, 'getAll').and.returnValue(response)

    scope.$onInit()

    expect(myService.getAll).toHaveBeenCalled();

    setTimeout(function() {
      expect(scope.todos).toEqual(expected)
      done()
    })
  });
});
      

<script src="https://cdn.jsdelivr.net/jasmine/2.6.1/jasmine.js"></script>
<script src="https://cdn.jsdelivr.net/jasmine/2.6.1/jasmine-html.js"></script>
<script src="https://cdn.jsdelivr.net/jasmine/2.6.1/boot.js"></script>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/jasmine/2.6.1/jasmine.css" />

<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.8/angular.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.8/angular-mocks.js"></script>
      

Run codeHide result


-1


source







All Articles