Asta
Asta

Reputation: 1579

When should I use $provide versus Jasmine Spies in my Angular JS Unit tests

I work on a large Angular App and initially we done a lot of our tests by using $provide to mock services. However we now have a lot of Jasmine Spies in our tests in order to stub and spy on services.

i.e

spyOn(myService, 'myMethod').andReturn 'myValue'

Should we really be using $provide for this or are there cases where spying on a service is the best approach?

In the Angular Tests they use spies for spying on Jquery which I would see as an external service.

spyOn(jq.prototype, 'on');

$provide seems to be used more for internal services.

  module(function($provide){
    $provide.provider('$exceptionHandler', $ExceptionHandlerProvider);
  });

There is also a Jasmine createSpy function but now I'm thinking that $provide should always take precedence over that.

Any insights or help in this would be appreciated.

Upvotes: 10

Views: 7742

Answers (1)

Michal Charemza
Michal Charemza

Reputation: 27052

From my own (limited) experience, I would say do whatever approach makes:

  • The test code simpler / clearer / shorter
  • Limits the assumptions about what the code your testing does internally.
  • Reduces its side-affects (like running actual Ajax requests)
  • Keeps the test as short as possible, in terms or run time.

Usually the spyOn approach works when, in order to do the above, I would like to stub a single method from a service / factory. If I need to mock an entire service / factory, then use $provide.

A few specific cases come to mind that require one or the other:

  • If you're testing a service, then to stub other methods from that service, you'll have to use spyOn

  • To ensure that extra dependencies aren't introduced later in the code under test, than $provide adds a bit more protection. Say, if you want to ensure that ServiceA only requires myMethod from ServiceB, then $provide I think would be the way to go, as if ServiceA calls any undefined methods from ServiceB during the test, errors would be raised.

    $provide.provider('ServiceB', {
        myMethod: function() {}
    });
    
  • If you want to mock a factory that returns a function, so:

    app.factory('myFactory', function() {
      return function(option) {
        // Do something here
      }
    });
    

    Which is used as:

    myFactory(option);
    

    Then to verify that some code calls myFactory(option) I think there is no alternative then to use $provide to mock the factory.

Just by the way, they're not mutually-exclusive options. You can use $provide and then still have spies involved. In the previous example, if you want to verify the factory was called with an option, you might have to:

var myFactorySpy = jasmine.createSpy();
$provide.provider('myFactory', myFactorySpy);

And then in the test at the appropriate point:

expect(myFactorySpy).toHaveBeenCalledWith(option);

Upvotes: 8

Related Questions