Isolating with Angular on Karma Jasmine
I have a suite of tests that are distributed across 2 modules.
The first module has tests and for its dependencies I declare mocks to test it without any influence from the dependent module, for example:
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 are passed when I only run them.
beforeEach(function(){
angular.mock.module('module1');
angular.mock.inject(function (_$rootScope_, _$q_, _$httpBackend_, ..., somethingFromModule1) {
});
})
Both tests when run with f (only works with them) work, but when I run the whole test case I get errors, especially regarding module declaration or $httpBackend
.
How can I get jasmine to run each test as if they were the only tests?
I seem to be messing around with angular / modules / $ httpBackEnd in every test and the changes are propagated when a new test is run.
Update 1 I have a jsFiddle presenting the issue. Problem structure:
- Some tests are done with a module-specific module
- Later another test wants to test the actual mocked module
- Since the first graph has already been loaded, we cannot overwrite it and the test fails.
In the JSFiddle the error about $ httpBackend without any glitches is because the request for the expected Get never gets, and it never gets because of a previously loaded empty module
It's important to note that the ORDER of the tests is the only thing that has to do with the failure, as in this JSFiddle with the same tests they go over.
I could of course place a test order and get around this, but I am aiming to find a way to make tests with isolation without worrying about other side effects of the test.
source to share
The problem you are facing has to do with how it $compileProvider
handles a new directive registered with a pre-existing name.
Shortly speaking; You are not overriding your old directive , you are creating a secondary with the same name. So the original implementation starts up and tries to grab baz.html
and $httpBackend
, since you haven't configured it to wait for that call.
See the updated script that changed two from the original fiddle .
- Do not enter
parentModule
in the specificationchildModule
. This line is unnecessary and this is part of the reason you are seeing these errors. Oh, andangular.module
evil in the land of trials. Try not to use it. - Decorate the original directive if you want to roll with the same name as the original, or call it something else. I decided to call it something else in the fiddle, but I put the code at the end of my answer to show the decorating method.
Here's a screenshot of what happens in the following scenario:
- Module A registers the directive
baz
. - Module B depends on the modulus of A .
- Module B registers a directive called
baz
.
As you can imagine, in order to prevent the modular system from becoming on its own, allowing people to rewrite eachothers directives - it will $compileProvider
simply register another directive with the same name. And both will work .
Take this ng-click example or article to describe how we can use multiple directives with the same name.
See the screenshot below for how your situation looks like.
The code on lines 71 through 73 is where you could implement solution # 2 that I mentioned at the beginning of my answer.
Finishing 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
};
});
Wherein:
$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
How about a call angular.module('parent', [])
?
You shouldn't be calling
angular.module('name', [])
in your specs unless you are using angular-module gist. And even then, it doesn't do much for you in the testing country.
Use only .mock.module
or window.module
, as otherwise you will kill your upcoming specs pertaining to the specified module, since you effectively killed the module definition for the rest of the spec to work.
In addition, the definition of the directive baz
from parentModule
'll automatically have access to your specification testModule
because of 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 call angular.module('parent', [])
in your spec, the original baz
definition will be loaded.
So the HTTP request flies away due to the nature of $compileProvider
supporting multiple directives of the same name, and that's the reason your spec package is failing.
Also, as a last note; You set up modules undefined
in your blocks beforeEach
. If the goal is to customize your test module, you are 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 property of $$moduleName
your mocked baz
definition is undefined
, whereas I assume you want it to be child
.
source to share