lonesomewhistle
lonesomewhistle

Reputation: 816

Testing Initialization of AngularJS Service with Mocks

I am trying to test the way an angular service responds to the values it gets from another service when it is initialized, and I'm having trouble finding a non-clunky way of doing it... much less one that works.

The service I'm trying to test looks kinda like this:

angular.module('myApp')
  .service('myService', function (Auth, DataGrabber) {
    var self = this;

    this.initialize = function () {
       this.user = Auth.getUser();

       if (!this.user.name) {
         DataGrabber.grabName();
       }

       if (!this.user.birthday) {
         DataGrabber.grabBirthday();
       }
    }

    this.initialize();
  })

And here is the not-so-smooth way I'm trying to test it:

describe('myService', function () {
   var myService;
   var dataGrabberMock;
   var authMock;
   var grabbedName;
   var grabbedBirthday;


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

   beforeEach(function() {
     grabbedName = false;
     grabbedBirthday = false;

     authMock = {
       getUser: function () {
         return { 
                  name: 'Bob',
                  birthday: 'Feb 22'
                }
       }
     }

     dataGrabberMock = {
       grabName: function () {
          grabbedName = true;
       },
       grabBirthday: function () {
          grabbedBirthday = true;
       }
     }

     module(function ($provide) {
       $provide.value('Auth', authMock);
       $provide.value('DataGrabber', dataGrabberMock);
     })
   })

   it ('just gets info if all data is present', function () {
     inject(function($injector) {
       myService = $injector.get('myService');
       expect(myService.user.name).toBe('Bob');
       expect(myService.user.birthday).toBe('Feb 22');
     });
   });

   it ('gets info if name is missing', function () {
     // how do I stub the new user response here? 
   })

   it ('calls birthdayGetter if birthday is missing', function () {
     // here too?
   })
})

What is the cleanest way to change the value that authMock provides for each test?

I've tried a zillion different combinations of inject(), $injector, $provide, and spyOn(). I finally got it working by setting up stubs and spies and calling myService.initialize() explicitly in the test, but then the initialization code is run twice for each test and that feels wrong.

I'm new to angular testing, so it's certainly possible that there's a way better approach that I'm overlooking as a newbie.

Thanks!

Upvotes: 2

Views: 1059

Answers (2)

Koen
Koen

Reputation: 549

I gave it another thought and I think it's cleaner to use separate describe blocks instead of using the separate methods.

describe('myService', function () {
  var myService;
  var dataGrabberMock;
  var authMock;
  var grabbedName;
  var grabbedBirthday;

  beforeEach(function () {
    module('myApp');
  });

  beforeEach(function () {
    grabbedName = false;
    grabbedBirthday = false;

    dataGrabberMock = {
      grabName: function () {
        grabbedName = true;
      },
      grabBirthday: function () {
        grabbedBirthday = true;
      }
    };

    spyOn(dataGrabberMock, 'grabName');
    spyOn(dataGrabberMock, 'grabBirthday');

    module(function ($provide) {
      $provide.value('Auth', authMock);
      $provide.value('DataGrabber', dataGrabberMock);
    });
  });

  describe('when all data is present', function () {
    beforeEach(function () {
      authMock = {
        getUser: function () {
          return {
            name: 'Bob',
            birthday: 'Feb 22'
          };
        }
      };
    });

    beforeEach(function () {
      inject(function ($injector) {
        myService = $injector.get('myService');
      });
    });

    it('just gets info', function () {
      expect(myService.user.name).toBe('Bob');
      expect(myService.user.birthday).toBe('Feb 22');
    });
  });

  describe('when name is missing', function () {
    beforeEach(function () {
      authMock = {
        getUser: function () {
          return {
            birthday: 'Feb 22'
          };
        }
      };
    });

    beforeEach(function () {
      inject(function ($injector) {
        myService = $injector.get('myService');
      });
    });

    it('gets info', function () {
      expect(dataGrabberMock.grabName).toHaveBeenCalled();
    });
  });

  describe('when birthday is missing', function () {
    beforeEach(function () {
      authMock = {
        getUser: function () {
          return {
            name: 'Bob',
          };
        }
      };
    });

    beforeEach(function () {
      inject(function ($injector) {
        myService = $injector.get('myService');
      });
    });

    it('calls birthdayGetter', function () {
      expect(dataGrabberMock.grabBirthday).toHaveBeenCalled();
    });
  });
});

Upvotes: 1

Koen
Koen

Reputation: 549

There is a typo in the assignment of dataGrabberMock

To change the value of authMock you can create a separate method for setting up the mock and a method for injecting the service and call both in your test.

beforeEach(function () {
  grabbedName = false;
  grabbedBirthday = false;

  dataGrabberMock = {
    grabName: function () {
      grabbedName = true;
    },
    grabBirthday: function () {
      grabbedBirthday = true;
    }
  };

  spyOn(dataGrabberMock, 'grabName');
  spyOn(dataGrabberMock, 'grabBirthday');

  module(function ($provide) {
    $provide.value('Auth', authMock);
    $provide.value('DataGrabber', dataGrabberMock);
  });
});

var authMock;

function setupAuthMock(name, birthday){
  authMock = {
    getUser: function () {
      return {
        name: name,
        birthday: birthday
      };
    }
  };
}

function setupAuthMockWithoutName(birthday){
  authMock = {
    getUser: function () {
      return {
        birthday: birthday
      };
    }
  };
}

function setupAuthMockWithoutBirthday(name){
  authMock = {
    getUser: function () {
      return {
        name: name
      };
    }
  };
}

function setupService() {
  inject(function ($injector) {
    myService = $injector.get('myService');
  });
}

it('just gets info if all data is present', function () {
  setupAuthMock('Bob', 'Feb 22');
  setupService();
  expect(myService.user.name).toBe('Bob');
  expect(myService.user.birthday).toBe('Feb 22');
});

it('gets info if name is missing', function () {    
  setupAuthMockWithoutName('Feb 30');
  setupService();
  expect(dataGrabberMock.grabName).toHaveBeenCalled();
});

it('calls birthdayGetter if birthday is missing', function () {
  setupAuthMockWithoutBirthday('Bob');
  setupService();
  expect(dataGrabberMock.grabBirthday).toHaveBeenCalled();
});

Upvotes: 2

Related Questions