最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

javascript - Testing async function with jasmine - Stack Overflow

programmeradmin0浏览0评论

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

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;
  });
};
Share Improve this question edited Aug 1, 2017 at 18:13 Mati asked Aug 1, 2017 at 18:00 MatiMati 1,1481 gold badge12 silver badges27 bronze badges
Add a comment  | 

3 Answers 3

Reset to default 13

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

Promise is from outside Angular world - you have to wait for result that will be available in next event queue tick - dirty (almost dirty) hack is to use setTimeout

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>

发布评论

评论列表(0)

  1. 暂无评论