2

I have a set of tests that are spread across 2 modules.

The first module has tests and for it's dependencies i declare mocks to test it without any influence from it's dependent module, like so:

        beforeEach(function(){
            angular.mock.module(function ($provide) {
                $provide.value("mockServiceFromModule1", mockServiceFromModule1);
                $provide.value("mockServiceFromModule2", mockServiceFromModule2);
            });

            angular.module('module1', []);
            angular.module('module2', []);
            angular.mock.module('moduleThatIAmTesting');

            angular.mock.inject(function (_$rootScope_, _$q_, _$httpBackend_, ..., somethingFromTestModule) {

            });

    })

The second module has a series of tests and all of them pass when i run only them.

        beforeEach(function(){

            angular.mock.module('module1');

            angular.mock.inject(function (_$rootScope_, _$q_, _$httpBackend_, ..., somethingFromModule1) {

            });

    })

Both tests when run with f(Running only them) works, but when i run the whole test suit i get errors, specially regarding module declaration or $httpBackend.

How can i make jasmine run each test as if they were the only tests?

It seems i am messing with the angular/modules/$httpBackEnd on each test and the changes are being propagated when it starts a new test.

Update 1 I have a jsFiddle representing the issue . The structure of the problem is :

  • Some test is ran with a mock dependant module
  • Later another test wants to test the actual mocked module
  • Since the first moldule was already loaded we can't overwritte it and the test fails.

On the JSFiddle the error about $httpBackend without nothing to flush is because the request for the expectedGet is never hit, and it's never hit because of the previously loaded empty module

It's important to notice that the ORDER of the tests is the only thing relevant to failing as in this JSFiddle with the same tests they pass.

I could of course make a tests order and bypass this but i am aiming to find a way to do the tests with isolation without worrying about other tests side effects.

FabioCosta
  • 3,069
  • 6
  • 28
  • 50
  • 2
    have you read this? http://stackoverflow.com/questions/14773269/injecting-a-mock-into-an-angularjs-service – franciscod Jul 15 '15 at 13:30
  • Yes it's not the mock module dependency that is the issue, and more that a full module was tested before and on the next test it's still loaded. So i have a dependent directive that i don't care for a given test trying to hit some html template that i don't want to serve and so on – FabioCosta Jul 16 '15 at 13:39

1 Answers1

1

The problem you are experiencing is due to the nature of how the $compileProvider handles a new directive being registered with a pre-existing name.

In short; You are not overriding your old directive, you are creating a secondary one with the same name. As such, the original implementation runs and tries to grab baz.html and $httpBackend throws as you have not set up an expectation for that call.

See this updated fiddle that did two changes from your original fiddle.

  1. Do not inject the parentModule to your childModule spec. That line is not needed and it is part of the reason you are seeing these errors. Oh and angular.module is evil in the land of tests. Try to not use it.
  2. Decorate the original directive if you wish to roll with the same name as the original one, or name it something else. I've opted for naming it something else in the fiddle, but I have supplied code at the end of my answer to show the decorator way.

Here's a screenshot of what happens in the following scenario:

  • Module A registers a directive called baz.
  • Module B depends on module A.
  • Module B registers a directive called baz.

As you can probably imagine, in order for the module system to not insta-gib itself by letting people overwrite eachothers directives - the $compileProvider will simply register another directive with the same name. And both will run.

Take this ng-click example or this article outlining how we can leverage multiple directives with the same name.

See the attached screenshot below for what your situation looks like. multi-dir-same-name

The code on lines 71 to 73 is where you could implement solution #2 that I mentioned in the start of my answer.


Decorating baz

In your beforeEach for testModule, replace the following:

$compileProvider.directive('baz', function () {
    return {
        restrict: 'E',
        template: '{{value}}<div ng-transclude></div>',
        controllerAs: 'bazController',
        controller: function ($scope, fooService) {
            $scope.value = 'baz' + fooService.get()
        },
        transclude: true
    };
});

With this:

$provide.decorator('bazDirective', function ($delegate) {
  var dir = $delegate[0];

  dir.template = '{{value}}<div ng-transclude></div>';

  dir.controller = function ($scope, fooService) {
      $scope.value = 'baz' + fooService.get();
  };

  delete dir.templateUrl;

  return $delegate;
});

jsFiddle showing the decorator approach


What about the call to angular.module('parent', [])?

You should not call angular.module('name', []) in your specs, unless you happen to be using the angular-module gist. And even then it's not doing much for you in the land of testing.

Only ever use .mock.module or window.module, as otherwise you will kill your upcoming specs that relate to the specified module, as you have effectively killed the module definition for the rest of the spec run.

Furthermore, the directive definition of baz from parentModule will automatically be available in your testModule spec due to the following:

angular.module('parent', []).directive('baz', fn());

angular.module('child', ['parent']);

// In your test: 

module('child'); // Will automatically fetch the modules that 'child' depend on. 

So, even if we kill the angular.module('parent', []) call in your spec, the original baz definition will still be loaded.

As such, the HTTP request flies off due to the nature of $compileProvider supporting multiple directives with the same name, and that's the reason your spec suite is failing.


Also, as a last note; You are configuring undefined modules in your beforeEach blocks. If the goal is to configure the module of your test, you are in the wrong.

The syntax is as follows:

mock.module('child', function ($compileProvider, /** etc **/) {
});

// Not this:
mock.module('child'); 
mock.module(function ($compileProvider, /** etc **/) {
});

This can be seen in the screenshot I posted. The $$moduleName property of your mocked baz definition is undefined, whereas I am assuming you would want that to be child.