I have the following directive:
function TopLevelMenuDirective ($userDetails, $configuration) {
return {
restrict:'A',
templateUrl: staticFilesUri + 'templates/TopLevelMenu.Template.html',
scope: {
activeTab: '='
},
link: function (scope, element, attributes) {
var userDetails = $userDetails;
if ($userDetails) {
scope.user = {
name: userDetails.name ? userDetails.name : 'KoBoForm User',
avatar: userDetails.gravatar ? userDetails.gravatar: (staticFilesUri + '/img/avatars/example-photo.jpg')
};
} else {
scope.user = {
name: 'KoBoForm User',
avatar: staticFilesUri + '/img/avatars/example-photo.jpg'
}
}
scope.sections = $configuration.sections();
scope.isActive = function (name) {
return name === scope.activeTab ? 'is-active' : '';
}
}
}
}
I want to mock the dependencies to unit test the different code paths with values known by the unit tests. I have the following sample unit test:
it('should set $scope.user to values passed by $userDetails',
inject(function($compile) {
var element = '<div top-level-menu></div>';
element = $compile(element)($scope);
$scope.$apply();
expect(element.isolateScope().user.name).toBe('test name');
expect(element.isolateScope().user.avatar).toBe('test avatar');
}
));
This gives me two problems.
First, since the template is in an external file, when it loads it tries to fetch it and errors out beacause the file is nowhere to be found, which is logical since it's in a test environment and not an actual server.
Second, there's no apparent way to mock the dependencies injected into the directive through its constructor. When testing controllers you can use the $controller
service, but since directives are instantiated indirectly by compiling an html tag with a passed scope, there's no way to instantiate it directly (e.g. there's no analogous $directive
). This impedes me from setting $userDetails.name
and $userDetails.gravatar
to 'test name'
and 'test avatar'
respectively.
How do I get the directive to compile properly and run with a custom $userDetails dependency?
To load the template file you must configure karma-ng-html2js-preprocessor
in karma.
First, visit this page and follow the installation instructions. Then, you need to add a couple of entries in your karma.config.js file:
files: [
'templates/*.html'
],
this tells karma to load all html files in the templates folder (if your templates are somewhere else, put that folder there).
preprocessors: { '**/*.html': 'ng-html2js' },
this tells karma to pass all html files through the ng-html2js
preprocessor, which then transforms them into angular modules that put the templates into the $templateCache
service. This way, when $httpBackend
queries the "server" for the template, it get's intercepted by the template cache and the correct html is returned. All fine here, except for the template's URL: it has to match the templateUrl
property in the directive, and ng-html2js
passes the full path as the uri by default. So we need to transform this value:
ngHtml2JsPreprocessor: {
cacheIdFromPath: function(filepath) {
var matches = /^\/(.+\/)*(.+)\.(.+)$/.exec(filepath);
return 'templates/' + matches[2] + '.' + matches[3];
}
},
this receives filepath
and passes it through a regular expression that extracts the path, file name and extension into an array. You then prepend 'templates/
to the file name and extension and you get the expected uri.
After all this is done making the template available is a matter of loading the module before your test is run:
beforeEach(module('templates/TopLevelMenu.Template.html'));
keep in mind, module
is an external service located in angular-mocks.js.
for injecting a custom service into the directive you need to override the service's provider:
beforeEach(module(function ($provide) {
$provide.provider('$userDetails', function () {
this.$get = function () {
return {
name: 'test name',
gravatar: 'test avatar'
};
}
});
}));
$provide
is the service that provides your providers. So, if you want to inject a mock dependency you override the provider here.
With that code executing before your test you'll have a mock $userDetails
service that returns your predefined strings.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With