0

I have a case to unit test a custom Angular directive myApp and have set up the necessary karma and jasmine config. In this test case I've tried to mock and stub the dependency module of myApp, which is the service - services.FuzzAPI. As I don't have access to the services.FuzzAPI file to mock it using sinon.

Following the Angular and Jasmine docs I setup up my spec file in the .spec file and following this similar SO answer

  1. In first beforeEach created a mock of the depedency accroding to this guide and registered the myApp modules to test using angular.mock.module.
  2. Then in the second beforeEach used the $injector to pass that mocked FuzzAPI for $compile step of myApp directive.

But even after mocking the dependency I get an $injector:modulerr telling me that the services.FuzzAPI is not available. Which tells me soemthing is a miss with the registration of the services.FuzzAPI dependency:

 LOG LOG: 'before mock'
 LOG LOG: 'after mock'
 Error: [$injector:modulerr] Failed to instantiate module myApp due to:
        Error: [$injector:modulerr] Failed to instantiate module services.FuzzAPI due to:
        Error: [$injector:nomod] Module 'services.FuzzAPI' is not available! You either misspelled the module name or forgot to load it

Question:

How can you mock a directive depedency in a Jasmine unit test?

Looking at the code I can see that this error is being thrown on line beforeEach(inject(function($rootScope, _$compile_, $injector) { as the console.log inside the inject beforeEach isn't tirggered before the error is thrown.

Gist of directive and test spec:

// my-app.js

(function() {

  var component = {
    id: "myApp",
    name: "My App",
    templateUrl: localStorage.getItem("baseURL") + "my-app/my-app.html"
  };

  component.ui = angular.module("myApp", ["services.FuzzAPI"]);
  component.ui.directive("myApp", widgetComponent);

  function widgetComponent(FuzzAPI) {

    // main widget container
    function widgetContainer(scope, element, params) {
      var api = new FuzzAPI(params);

      scope.greeting = "Hello World";

      var setGreeting = function(message){
        scope.greeting = message;  
      };

      api.onDataEvent("onFuzzEvent", function(data) {
        scope.greeting = data;
      });


      element.on("$destroy", function() {     
        api.unregister();
        scope.$destroy();
      });
    }

    return {
      scope: {},
      replace: true,
      link: widgetContainer,
      templateUrl: component.templateUrl
    };
  }
})();

//my-app.spec.js

describe("myApp", function() {

  var $scope, $compile, $provide, $injector, element, fuzzAPIMock, FuzzAPIProvider;

  beforeEach(function(){

    //mock service FuzzAPI via service provider
    fuzzAPIMock = {
      greeting : "123",
      initializeCurrentItem : function () {
        return true;
      }
    };

    console.log("before mock");

    //register module and mock dependency
    angular.mock.module(function($provide) {
            $provide.value('services.FuzzAPI', fuzzAPIMock);
    });

    angular.mock.module("myApp");

    console.log("after mock");


  });

  beforeEach(inject(function($rootScope, _$compile_, $injector) {

    console.log("in inject..");
    FuzzAPIProvider = $injector.get('services.FuzzAPI');
    $scope = $rootScope.$new(), //create instance of rootScope
    $compile = _$compile_; 
    console.log("in inject before compile..");
    element = $compile("<my-app></my-app>")($scope); // compile attaches directives to HTML template
    console.log("in inject after compile..");
    $scope.$digest(); //loop through DOM watchers, check values and trigger listeners
  }));


});
Brian J
  • 5,416
  • 19
  • 94
  • 189

1 Answers1

2

Error: [$injector:nomod] Module 'services.FuzzAPI' is not available! You either misspelled the module name or forgot to load it

means that it is services.FuzzAPI module that should be mocked, not service. According to provided code, service name is FuzzAPI, and it should be mocked with constructor function, not object.

In order for the missing module to not cause an error, it should be stubbed. It can be stubbed once, in top-level describe block:

beforeAll(() => {
  angular.module('services.FuzzAPI', []);
});

It's not possible to restore stubbed module (at least without a hack), but it's not a problem if real module shouldn't be presented in tests.

Then services can be mocked as usual. If it's FuzzAPI:, it should be

fuzzAPIMock = jasmine.createSpy('').and.returnValue({
  greeting : "123",
  initializeCurrentItem : jasmine.createSpy('').and.returnValue(true)
});

angular.mock.module(function($provide) {
  $provide.value('FuzzAPI', fuzzAPIMock);
});

It is always preferable to use Jasmine spies as mocked/stubbed functions.

Estus Flask
  • 150,909
  • 47
  • 291
  • 441