2

I’m writing a unit test for function getNextPage(). I set the test: expect(this.anotherService.resources).toEqual(3); I got error: Expected undefined to equal 3 when running the test. I logged the anotherService.resources and it returned 3 in console. Not sure why it's not working.

Test

describe('Test for someController', function() {
  beforeEach(function() {
    module('someApp');
    return inject(function($injector) {
      var $controller;
      var $q = $injector.get('$q');

      this.rootScope = $injector.get('$rootScope');
      $controller = $injector.get('$controller');
      this.state = $injector.get('$state');
      this.stateParams = {
        id: 1,
      }
      this.location = $injector.get('$location')
      this.timeout = $injector.get('$timeout')
      this.upload = $injector.get('$upload')
      this.someService = {
        getItemList: function(res) {
          var deferred = $q.defer();
          deferred.resolve({
            data: {
              totalRows: 2,
              rows: 3,
            }
          });
          return deferred.promise;
        },
        pages: jasmine.createSpy(),

        memberIds: 1,
        currEng: []
      };
      this.anotherService = {
        resources: {}
      };
      this.scope = this.rootScope.$new();
      this.controller = $controller('someController', {
        '$scope': this.scope,
        '$rootScope': this.rootScope,
        '$state': this.state,
        '$stateParams': this.stateParams,
        '$location': this.location,
        '$timeout': this.timeout,
        '$upload': this.upload,
        'someService': this.someService,
      });
      this.scope.$digest();
    });
  });

  it('should be defined', function() {
    expect(this.controller).toBeDefined();
    expect(this.scope.ss).toEqual(this.someService);
  });

  it('should run the getNextPage function', function() {
    this.scope.getNextPage();
    this.scope.$digest();
    console.log(this.anotherService.resources);        // this is showing as Object {} in terminal
    expect(this.anotherService.resources).toEqual(3);
  });

Code:

someapp.controller('someController', resource);
resource.$inject = ['$scope', '$state', '$stateParams', '$location','$timeout','$upload', 'someService', 'anotherService'];
function resource($scope, $state, $stateParams,$location,$timeout, $upload, someService, anotherService) {

      $scope.fileReaderSupported = window.FileReader != null && (window.FileAPI == null || FileAPI.html5 != false);

      $scope.ss = EsomeService;
      $scope.as = anotherService;
      $scope.getNextPage = getNextPage;


      function getNextPage(options){

        var o = options || {selected:1};
        var start = (o.selected-1)*10 || 0;
        someService.currPage = o.selected;

        someService.getItemList($stateParams.id,'F', start).then(function (res){
          anotherService.resources = res.data.rows;
          console.log(anotherService.resources)   // this shows LOG: 3 in terminal
          someService.numResults = res.data.totalRows;
          someService.pageNumbers = someService.pages(res.data.totalRows,10);
        })
      }
});
2
  • 1
    You need to wait for the promise. Commented Jun 7, 2016 at 18:22
  • Why are you using a promise at all? Commented Jun 7, 2016 at 18:22

1 Answer 1

5

The value of this.anotherService.resources is still {} in your test because the code in the following then callback is executed after your test runs, asynchronously:

someService.getItemList($stateParams.id,'F', start).then(function (res){
    anotherService.resources = res.data.rows;
    console.log(anotherService.resources)
    someService.numResults = res.data.totalRows;
    someService.pageNumbers = someService.pages(res.data.totalRows,10);
})

Although in getItemList you resolve the promise synchronously

getItemList: function(res) {
    var deferred = $q.defer();
    deferred.resolve({
        data: {
            totalRows: 2,
            rows: 3,
        }
    });
    return deferred.promise;
},

... it actually does not call the then function on the promise immediately when you call deferred.resolve. When you think of it, that would not make sense either, because the promise must first be returned to the caller before the caller can attach the then call back to it. Instead it calls the then callback asynchronously, i.e. after all currently executing code finishes with an empty call stack. This includes your test code! As stated in the Angular documentation:

then(successCallback, errorCallback, notifyCallback) – regardless of when the promise was or will be resolved or rejected, then calls one of the success or error callbacks asynchronously as soon as the result is available.

and also in the testing example in the same documentation:

// Simulate resolving of promise
deferred.resolve(123);
// Note that the 'then' function does not get called synchronously.
// This is because we want the promise API to always be async, whether or not
// it got called synchronously or asynchronously.

How to test asynchronous code

First, you could let getNextPage return a promise -- the same promise that getItemList returns:

function getNextPage(options){
    var o = options || {selected:1};
    var start = (o.selected-1)*10 || 0;
    someService.currPage = o.selected;
    // store the promise in a variable
    var prom = someService.getItemList($stateParams.id,'F', start);
    prom.then(function (res){
        anotherService.resources = res.data.rows;
        console.log(anotherService.resources)   // this shows LOG: 3 in terminal
        someService.numResults = res.data.totalRows;
        someService.pageNumbers = someService.pages(res.data.totalRows,10);
    });
    return prom; // return that promise
}

And then you can use then on getNextPage(), which will execute in sequence with any other then callbacks attached to it, so after the then callback in the above piece of code.

Jasmine's done can then be used to tell Jasmine the test is asynchronous and when it has completed:

// The presence of the `done` parameter indicates to Jasmine that 
// the test is asynchronous 
it('should run the getNextPage function', function(done) { 
    this.scope.getNextPage().then(function () {
        this.scope.$digest();
        console.log(this.anotherService.resources);
        expect(this.anotherService.resources).toEqual(3);
        done(); // indicate to Jasmine that the asynchronous test has completed
    });
});
Sign up to request clarification or add additional context in comments.

6 Comments

i tried using .bing(this) it returned me with a different error. TypeError: undefined is not an object (evaluating 'this.scope.getNextPage')
That might indicate that this is already not what you think it is when arriving at the it call. Can you please explain what you want this to be, and maybe provide the complete context in which it is called?
I think you were right, I tried console.log(this.anotherService.resources) and its returning me an object instead. I’ve set the data in someService to be totalRows: 2 and rows: 3. I’m expecting rows to pass through the promise and return 3 for anotherService.resource.
Can you provide more code in your question so I can see where you have placed the tests? The result of console.log(this............) depends on where you put it. Every line of code with this is context sensitive, so in your question please make sure the code-context is clear, otherwise we're looking in the dark.
here is a more complete version of the code, hopefully things would make more sense. Thanks!
|

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.