Unittest with angular, karma and es6 - injecting a service - angularjs

I'm trying to write a test for this directive:
export default angular.module('app.page.section.accountBalance', [])
.directive('accountBalance', accountBalanceConfig);
function accountBalanceConfig() {
return {
restrict: 'E',
replace: true,
scope: {
data: '='
},
template: require('./account-balance.tpl.html'),
controller: accountBalanceController,
controllerAs: 'accountBalance'
}
}
class accountBalanceController {
constructor($scope, domFactory, userService, $filter) {
// controller logic
}
}
As you can see, the directive's controller depend on domFactory which is a custom service I created that compiles the DOM:
export class DomGenerator {
/**
* #constructor
* inject dependencies
*/
constructor() {
'ngInject';
}
// service logic
}
This is the test:
import { DomGenerator } from '../../shared/services/dom-genereator/dom-generator.srv';
import compile from '../compile/compile.drv';
import module from './account-balance.drv';
describe('account balance', () => {
var $rootScope, $compile, $location, $window, $document, userService, domFactory, $provide;
beforeEach(() => {
domFactory = new DomGenerator();
userService = new UserData();
});
beforeEach(angular.mock.module(compile.name));
beforeEach(angular.mock.module(module.name));
beforeEach(inject(($injector) => {
$rootScope = $injector.get('$rootScope');
$compile = $injector.get('$compile');
}));
it('renders account balance widget headline', () => {
var element = $compile(`<account-balance data="{headline: {title: 'account balance'}}"></account-balance>`)($rootScope);
$rootScope.$digest();
expect(element.html()).to.contain('account balance');
});
});
class UserData {
constructor() {
}
}
This is the error I get:
Error: [$injector:unpr] Unknown provider: domFactoryProvider <- domFactory
http://errors.angularjs.org/1.5.8/$injector/unpr?p0=domFactoryProvider%20%3C-%20domFactory
at webpack:///~/angular/angular.js:68:0 <- spec.bundle.js:16824:13
at webpack:///~/angular/angular.js:4511:0 <- spec.bundle.js:21267:20
at Object.getService [as get] (webpack:///~/angular/angular.js:4664:0 <- spec.bundle.js:21420:40)
at webpack:///~/angular/angular.js:4516:0 <- spec.bundle.js:21272:46
at getService (webpack:///~/angular/angular.js:4664:0 <- spec.bundle.js:21420:40)
at injectionArgs (webpack:///~/angular/angular.js:4688:0 <- spec.bundle.js:21444:59)
at Object.invoke (webpack:///~/angular/angular.js:4710:0 <- spec.bundle.js:21466:19)
at $controllerInit (webpack:///~/angular/angular.js:10354:0 <- spec.bundle.js:27110:35)
at nodeLinkFn (webpack:///~/angular/angular.js:9263:0 <- spec.bundle.js:26019:35)
at compositeLinkFn (webpack:///~/angular/angular.js:8620:0 <- spec.bundle.js:25376:14)
Which means that I didn't inject the service to the directive's controller.
What am I doing wrong?
Isn't
beforeEach(() => {
domFactory = new DomGenerator();
userService = new UserData();
});`
enough?

You should do something like that , You should mock the service (Use sinon).
import accountBalanceController from 'path for account balance controller'
// import accountBalanceConfig
// import html template
import { DomGenerator } from '../../shared/services/dom-genereator/dom-generator.srv';
import compile from '../compile/compile.drv';
import module from './account-balance.drv';
import sinon from 'sinon'
describe('account balance', () => {
let $rootScope, makeController,DomGeneratorFactory,$scope,$filter,userService;
beforeEach(inject((_$rootScope_,_$scope_,_$filter_) => {
$rootScope = _$rootScope_;
$scope = _$scope_;
$filter = _$filter_;
DomGeneratorFactory = sinon.createStubInstance(DomGenerator);
// Create mock to user Service
makeController = () => {
return new accountBalanceController($scope, DomGeneratorFactory, userService, $filter);
};
}));
describe('Controller', () => {
// controller specs
it('has a name property [REMOVE]', () => { // erase if removing this.name from the controller
let controller = makeController();
expect(controller).to.have.property('name');
});
it('includes the intended template', () => {
expect(accountBalanceConfig.template).to.equal(htmlTemplate);
});
});
});

Related

Mocking a service thats provided in a component controller in a unit test

This is the error I'm seeing when trying to run my unit test ..
Expected undefined to be defined.
TypeError: undefined is not an object (evaluating '$rootScope.$digest')
Module 'ThirdPartyModule' is not available! You either misspelled the module name or forgot to load it. If registering a module ensure that you specify the dependencies as the second argument.
Any ideas how to mock the testService so that I can still compile my component?
test.component.spec.ts
import { TestModule } from '../index';
describe('Component: testComponent', () => {
let $rootScope: angular.IScope;
let element: angular.IAugmentedJQuery;
beforeEach(() => {
angular.mock.module('ui.router');
angular.mock.module(TestModule.name);
});
beforeEach(inject((
_$rootScope_: angular.IScope,
$compile: angular.ICompileService,
_$state_: angular.ui.IStateService) => {
$rootScope = _$rootScope_;
element = angular.element('<test></test>');
element = $compile(element)($rootScope);
}));
it('should verify component compiled and rendered template', () => {
expect(element).toBeDefined();
$rootScope.$digest();
let link = element.find('a');
expect(link.text()).toContain('Click this link!');
});
});
test.module.ts
import { TestComponent } from './test';
export let TestModule: ng.IModule = angular.module(
'test', // my module name
['ui.router', 'ThirdPartyModule']) // dependencies, ThirdPartyModule contains testService
.component('test', new TestComponent());
test.component.ts
import { TestComponentController } from './test.component.controller';
export class TestComponent implements ng.IComponentOptions {
public template: string = '<a ng-if="ctrl.serviceReturned">Click this link!</a>';
public controller: Function = TestComponentController;
public controllerAs: string = 'ctrl';
constructor() {}
}
test.component.controller.ts
export class TestComponentController {
public serviceReturned: boolean = false;
constructor(private testService: any) {
if (this.testService.isDone()) {
this.serviceReturned = true;
}
}
}
TestComponentController.$inject = ['testService'];
Don't you need to add 'ThirdPartyModule' in an angular.mock.module?

how to unit test custom $filter by mocking in ES6 class for angular controller

I have a ES Class written for a angular controller and i am trying to write jasmine tests using angular-mock.
In the constructor I initialize $filter to this.i18n = $filter('i18n), which is basically a localization filter that takes a key, value and returns teh localized value for the key.
My problem is that since teh constructor of the class has the $filter and is later on used in the class methods. My unit tests fail. How could i test using $filter such that my tests dont fail. I am looking to mock the custom filter
Here is the exception that i get
TypeError: this.i18n is not a function
at UsersCtrl.$onInit (test-context.js:35081:41)
at Object.<anonymous> (test-context.js:35031:31)
Here is the ES6 Class
class UsersCtrl {
constructor($filter) {
this.i18n = $filter('i18n');
this.users = [];
}
//Life cycle Hooks: Initialization
$onInit() {
this.users = [
{name: this.i18n('USERS')}
];
}
}
UsersCtrl.$inject = ['$filter'];
angular.module('app', []).controller('UsersCtrl',UsersCtrl);
export default UsersCtrl;
Here is the Unit test that i have
Import UsersCtrl from './users-controller.js';
describe("given a new User Page", () => {
var UserController;
beforeEach(() => {
angular.mock.module('app');
});
describe("when initialising has completed", () => {
beforeEach(() => {
inject(($rootScope, $controller, $filter) => {
const scope = $rootScope.$new();
const filter = $filter
UserController = $controller("UsersCtrl", { $scope: scope, $filter: filter});
});
});
it("then users array for tab content should be empty initially",() => {
const expectedActive = [];
expect(UserController.users).toEqual(expectedActive);
});
});
});
The issue is it runs the test but fails as its unable to initialize with this.i18n = $filter('i18n).How should i initialize the test to pass $filter into the tests? Could i mock the filter as i dont really want to test the filter here
You could either:
Mock the entire $filter service and inject that into the controller when you instantiate it in your test; or
Mock the individual filter. You can then use $provide.value(key, value) when you load your module, where the key is the filter name plus a suffix: 'Filter' (this is something angular does internally when registering filters).
An example of both:
/* Angular App */
(function() {
"use strict";
class UsersCtrl {
constructor($filter) {
this.i18n = $filter('i18n');
this.users = [];
}
$onInit() {
this.users = [{
name: this.i18n('USERS')
}];
}
}
UsersCtrl.$inject = ['$filter'];
angular
.module('app', [])
.controller('UsersCtrl', UsersCtrl);
})();
/* Unit Test */
(function() {
"use strict";
describe('given a new User Page', () => {
let UsersController;
describe('MOCK entire $filter service: when initialising has completed', () => {
beforeEach(() => {
module('app');
inject(($rootScope, $controller) => {
UsersController = $controller("UsersCtrl", {
$scope: $rootScope.$new(),
$filter: () => {
// $filter will be a function that returns a noop function (or whatever we want)
return angular.noop;
}
});
});
});
it('then users array for tab content should be empty initially', () => {
const expectedActive = [];
expect(UsersController.users).toEqual(expectedActive);
});
});
describe('MOCK individual \'i18n\' filter: when initialising has completed', () => {
beforeEach(() => {
module('app', ($provide) => {
// provide a mock (noop function) for our filter.
// Note naming convention, our filter name + 'Filter' suffix.
$provide.value('i18nFilter', angular.noop);
});
inject(($rootScope, $controller, $filter) => {
UsersController = $controller("UsersCtrl", {
$scope: $rootScope.$new(),
$filter: $filter
});
});
});
it('then users array for tab content should be empty initially', () => {
const expectedActive = [];
expect(UsersController.users).toEqual(expectedActive);
});
});
});
})();
<link rel="stylesheet" href="//cdn.jsdelivr.net/jasmine/2.0.0/jasmine.css" />
<script src="//cdn.jsdelivr.net/jasmine/2.0.0/jasmine.js"></script>
<script src="//cdn.jsdelivr.net/jasmine/2.0.0/jasmine-html.js"></script>
<script src="//cdn.jsdelivr.net/jasmine/2.0.0/boot.js"></script>
<script src="//ajax.googleapis.com/ajax/libs/angularjs/1.5.8/angular.js"></script>
<script src="//ajax.googleapis.com/ajax/libs/angularjs/1.5.8/angular-mocks.js"></script>

Use resolve with es6 class syntax

I want to use resolve to check if user is present on localStorage before controller fires up but can't get it to work.
Example:
//loginDirective.js
import {LoginController as controller} from './login.controller';
import template from './login.html';
export const loginDirective = () =>{
return{
controller,
template,
scope:{},
controllerAs:'vm',
replace: true,
restrict: 'E'
}
}
//login.js
import {loginDirective} from './login.directive';
import angular from 'angular';
import uiRouter from 'angular-ui-router';
export const login = angular.module('admin', [uiRouter])
.config(($stateProvider) => {
$stateProvider.state('login', {
url : '/login',
template: '<login></login>',
resolve : {
loggedUser: function () {
if(localStorage.getItem('user')){
return true;
}
return false;
}
}
})
})
.directive('login', loginDirective);
//LoginController
'use strict';
class LoginController {
constructor($state, AuthenticationService, loggedUser) {
this.$state = $state;
this.AuthenticationService = AuthenticationService;
this.email = '';
this.password = '';
this.loggedIn = loggedUser;
}
login = () => {
this.AuthenticationService.Login(this.email, this.password).then((result) => {
this.loggedIn = result;
if (this.loggedIn === true) {
this.$state.go('home', {}, {reload: true});
}
});
}
logout = () => {
this.AuthenticationService.Logout();
this.loggedIn = false;
this.$state.go('login', {location: true});
}
}
LoginController.$inject = ['$state', 'AuthenticationService', 'loggedUser'];
export {LoginController}
This throws Unknown provider: loggedUserProvider <- loggedUser
Note:
I am using webpack, gulp and babel to transpile. WebPack has babel stage 1 enabled.
I can see the resolve method in this.$state but that is not the way I want to access it.
Your 'login' state definition has no controller. So ui-router doesn't instantiate the login controller. So it can't pass anything to its constructor.
The controller is not the controller of the state. It's the controller of a directive that happens to be used in the template of the state. So that can't work.
Read https://ui-router.github.io/guide/ng1/route-to-component to understand how you must do: either use a controller and a template, or use a component.

Cannot get angular directive to display template in test with typescript, karma and jasmine, what could be wrong?

I have a very simple directive like this:
class HelloDirective implements ng.IDirective{
public link: (scope: ng.IScope, element: ng.IAugmentedJQuery, attrs:ng.IAttributes) => void;
public template = '<div>Hello world</div>';
public scope = {};
constructor(){
}
public static Factory()
{
var directive = (/*list of dependencies*/) =>
{
return new HelloDirective(/*list of dependencies*/);
};
directive['$inject'] = ['/*list of dependencies*/'];
return directive;
}
}
export { HelloDirective};
This works fine when I use it in an app but when I try to use it in a test like this:
import angular = require('angular');
import ngMock = require('angular-mocks/ngMock');
import IAngularStatic = angular.IAngularStatic;
import {HelloDirective} from "../HelloDirective";
describe('helloDirective tests', () => {
var $compile : ng.ICompileService;
var $rootScope : ng.IRootScopeService;
var app: ng.IModule;
var helloElement: any;
beforeEach(() => {
app = angular.module('helloApp', [ngMock]);
app.directive('hello', [HelloDirective.Factory()]);
var elementHtml = '<hello/>';
inject(function (_$compile_: any, _$rootScope_: any) {
$rootScope = _$rootScope_;
$compile = _$compile_;
});
helloElement = $compile(elementHtml)($rootScope);
$rootScope.$digest();
});
it('should display helloDirective correctly', () => {
expect(helloElement.text()).toBe('Hello world');
});
});
I get: Expected '' to be 'Hello world'.
If I use regular html as element e.g.
<div> Hello world</div>
instead of
<hello/>
the test works fine and when I debug I see that the directives factory method is called.
What could be wrong?
Any help would be much appreciated.

How to mock a provider

My Angular 1.3 application is using the angular-translate library. In my Karma tests I'm attempting to mock the $translate provider with a Mock object I have created.
The mock object is called MockTranslate and it belongs to the myMocks module. I'm not including the source for MockTranslate in the question as it's not relevant to the question.
The subject of my test is a controller and I can quite easily mock $translate using the following:
module('myMocks');
inject(function($controller, MockTranslate) {
$controller("MyController", {
$translate: MockTranslate.create(translations);
});
});
The above mocking works, however my preference would be to mock the provider using the angular.mock.module with something like:
module('myMocks');
module("myModule", function($provide) {
$provide.provider("$translate", function(MockTranslate) {
return MockTranslate.create(translations);
});
});
But I get the following error when I run my tests:
Error: [$injector:modulerr] Failed to instantiate module function ($provide) due to: Error: [$injector:unpr] Unknown provider: MockTranslate
How do I mock a provider using angular.mock.module?
If I understood the task correctly then here is a working example:
angular.module('translateApp', [])
.controller('translateCtrl', function ($scope, $translate) {
$scope.translate = function(message) {
return $translate.translate(message);
};
})
.provider({
$translate: function() {
this.$get = function () {
return {
translate: function (msg) {
return 'OriginalTranslate: ' + msg;
}
};
};
}
});
describe('Translate Controller Test', function() {
var mockScope;
var mockTranslate;
beforeEach(module('translateApp', function($provide) {
$provide.provider('MockTranslate', function() {
this.$get = function () {
return {
translate: function (msg) {
return 'MockTranslate: ' + msg;
}
};
}
});
$provide.provider('$translate', function() {
this.$get = function (MockTranslate) {
return {
translate: function (msg) {
return MockTranslate.translate(msg);
}
};
}
});
}));
beforeEach(inject(function($controller, $rootScope, $translate) {
mockScope = $rootScope.$new();
mockTranslate = $translate;
$controller('translateCtrl', {
$scope: mockScope,
$translate: mockTranslate
});
}));
it('Translates messages', function () {
expect(mockScope.translate('cool message')).toEqual('MockTranslate: cool message');
});
});

Resources