52

I'm testing service A, but service A depends on service B (i.e. service B is injected into service A).

I've seen this question but my case is a bit different because in my opinion it makes more sense to mock service B instead of injecting an actual instance of service B. I'd mock it with a jasmine spy.

Here's a sample test:

describe("Sample Test Suite", function() {

  beforeEach(function() {

    module('moduleThatContainsServiceA');

    inject([
      'serviceA', function(service) {
        this.service = service;
      }
    ]);

  });

  it('can create an instance of the service', function() {
    expect(this.service).toBeDefined();
  });
});

The error I get is:

Error: Unknown provider: serviceBProvider

How could I do something like this?

1
  • FWIW: I've asked a QUnit version of this question here on CodeReview.SE. Commented Aug 6, 2015 at 7:40

5 Answers 5

46

Actually in AngularJS Dependency Injection uses the 'last wins' rule. So you can define your service in your test just after including your module and dependencies, and then when service A that you're testing will request service B using DI, AngularJS will give mocked version of service B.

This is often is done by defining new module like MyAppMocks, putting mocked services/values there and then just adding this module as dependency.

Kind of (schematically):

beforeEach(function() {
  angular.module('MyAppMocks',[]).service('B', ...));
  angular.module('Test',['MyApp','MyAppMocks']);
  ...
Sign up to request clarification or add additional context in comments.

6 Comments

You just saved my life! :D I was killing myself trying to inject a mocked service into another service that depended on it, but only the tests would use the mocked version, not the injected service. Now I have a separate mock module that I load after the app module that overwrites the wanted services. Works like a charm!
Thomas, could you share some details about your solution? I have 2 modules, each of them contains a service, and service_1 from the first module is injected in service_2 in the second module. I'm creating a mock module with service_1 which is supposed to overwrite original service_1. And it is overwritten, but only in tests, so when I call service_2, it's still using original service_1 inside.
While I'm sure that this answer is correct, I'm a bit confused with the "...". I'm thinking of this in the context where I want to test out a controller in a module which depends on "MyApp". What happens in the ...? Am I testing the functionality of the "Test" module now?
@clearf in "..." you define mock of service 'B' that provides necessary functionality for controller to be tested. So, basically, you implement only part of service B by creating spies (for example) and make sure at the end of the test that all needed methods of service 'B' were called.
|
24

I was doing this in CoffeeScript and found an extra gotcha. (Also, I found the code on this page to be confusingly terse.) Here's a complete working example:

describe 'serviceA', ->
   mockServiceB = {}

   beforeEach module 'myApp' # (or just 'myApp.services')

   beforeEach ->
      angular.mock.module ($provide) ->
         $provide.value 'serviceB', mockServiceB
         null

   serviceA = null
   beforeEach inject ($injector) ->
      serviceA = $injector.get 'serviceA'

   it 'should work', ->
      expect( true ).toBe( true )
      #serviceA.doStuff()

Without explicitly returning null after $provide.value, I kept getting Error: Argument 'fn' is not a function, got Object. I found the answer in this Google Groups thread.

4 Comments

Try using empty return instead of null. This way your generated javascript will not have additional return null line at the end of the function. Instead, your beforeEach function will just return nothing.
Im trying to use a similar code at the moment, but I get a SyntaxError: Unexpecte string 'serviceA' if i'd use your code. Any idea on how to solve this? -- Also using Coffeescript
@MathieuBrouwers, you should probably open a new question. I'm not sure what you're referring to exactly, and I'd have to see your code to answer.
I found the problem by now, it was when I tried to actually test the CoffeeScript instead of the compiled to javascript code. I guess my comment is kinda useless and could be removed now.
20

The Valentyn solution worked for me, but there is another alternative.

beforeEach(function () {

    angular.mock.module("moduleThatContainsServiceA", function ($provide) {
                $provide.value('B', ...);
            });
});

Then when AngularJS service A request the Service B by Dependency Injection, your mock of Service B will be provided instead of the Service B from moduleThatContainsServiceA.

This way you don't need to create an additional angular module just to mock a Service.

2 Comments

Excellent. In my case '...' were replace with '{}'. Bam, totally removed a dependency.
Issue with this is that you still mock your service inside the test. Say that you need to mock this in several places and something changes. Then you would have to change each file instead of changing in one place. For sake of maintainability Valentyns answer should be the accepted one (which it is).
6

I find the simplest method is just to inject service B and mock it. e.g. Service car depends on service Engine. Now we need to mock Engine when testing Car:

describe('Testing a car', function() {
      var testEngine;

  beforeEach(module('plunker'));
  beforeEach(inject(function(engine){
    testEngine = engine;
  }));

  it('should drive slow with a slow engine', inject(function(car) {
    spyOn(testEngine, 'speed').andReturn('slow');
    expect(car.drive()).toEqual('Driving: slow');
  }));
});

Reference: https://github.com/angular/angular.js/issues/1635

Comments

1

This is what worked for me. The key is defining a real module to be mocked. Calling angular.mock.module makes the real module mockable and allows things to be connected.

    beforeEach( ->
        @weather_service_url = '/weather_service_url'
        @weather_provider_url = '/weather_provider_url'
        @weather_provider_image = "test.jpeg"
        @http_ret = 'http_works'
        module = angular.module('mockModule',[])
        module.value('weather_service_url', @weather_service_url)
        module.value('weather_provider_url', @weather_provider_url)
        module.value('weather_provider_image', @weather_provider_image)
        module.service('weather_bug_service', services.WeatherBugService)

        angular.mock.module('mockModule')

        inject( ($httpBackend,weather_bug_service) =>
            @$httpBackend = $httpBackend
            @$httpBackend.when('GET', @weather_service_url).respond(@http_ret)
            @subject = weather_bug_service
        )
    )

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.