I am trying to test directives and I have looked at a couple examples but I can't find one similar to mine. My current problem is that I have a controller attached to my directive and Angular can't seem to find my custom services which are injected into the controller but it can find it's own services.
Directive
export class MyDirective implements ng.IDirective {
constructor(private clientAppState: IClientAppState) { }
static instance(clientAppState: IClientAppState): ng.IDirective {
return new LocationAccessCodeDirective(clientAppState);
}
restrict = 'E';
templateUrl = 'myDirectiveDirectory.html';
controller = MyDirectiveController;
controllerAs = 'myDirectiveController';
scope: {};
}
angular
.module('myDirective')
.directive('myDirectiveName', MyDirective.instance);
MyDirectiveController
export class MyDirectiveController implements ILocationAccessCodeScope {
constructor(protected $analytics: angulartics.IAnalyticsService,
public errorMessageService: IErrorMessageService //Custom Service
)
{
//Initializations
}
Dependency File
MyDirectiveController.$inject = [
'$analytics',
'ErrorMessageService',
];
Spec
describe("myDirective", () => {
var scope;
beforeEach(() => {
angular.mock.module(
'DirectiveModule',
'ServiceModule',
'ng',
'myDirectiveDirectory.html'
);
mockErrorMessageService = sinon.stub(new MockErrorMessageService());
angular.mock.module(($provide): void => {
$provide.value('ErrorMessageService', mockErrorMessageService);
});
inject(($compile, $rootScope) => {
scope = $rootScope.$new();
var element = angular.element("<directive-tag></directive-tag>");
var comEl = $compile(element)(scope);
$rootScope.$digest();
console.log(element[0].outerHTML);
//Note that if I remove the (scope)
//...from $compile(element)(scope)
//my directive is printed out
});
});
describe('first directive test', () => {
it('first it', () => {
console.log("it");
});
});
});
Error Message
Error: [$injector:unpr] Unknown provider: ErrorMessageServiceProvider <- errorMessageService
For some reason Angular injects its own Analytics service into my controller but it cannot find my own service and I can't seem to figure out why, and I'm pretty sure I have included all of the relevant files into my Karma.config.js file
Also note, this may be irrelevant but I tried inject a random custom service into an beforeEach block and I got the same error, but when injecting it using inline annotation it found the service, I'm not sure why this may be because I'm not minifying my code, and as you can see from the dependency list it is also being inject as a list.
The problem was that I was including my dependency file after my controller file, but for some reason it started working when I included my dependency file first.
Related
I have just jumped to another project and, basically, I have been asked to write Unit tests. Since I have already know Protractor for e2e testing, I now switched to Karma and Jasmine to carry out unit testing. I have already downloaded karma, jasmine, karma-jasmine, and karma-chrome-launcher. I installed angular-mocks as well so I should be ready to start. I have read many things on the internet but, now, what I really need is a concrete example of a real app to figure out how to start writing tests. I don't need easy examples but concrete examples and full explenations. Books and useful links are appreciated as well. Thanks in advance for your help/time.
describe('ServiceBeingTested Name', (): void => {
var mockFirstDependency;
var mockSecondDependency;
var TestedService;
//Mock all dependencies
beforeEach((): void => {
angular.mock.module('moduleServiceIsIn'); //Register the module which the service is in
mockFirstDependency = sinon.stub(new MockFirstDependency());//Sinon if useful for mocking
mockSecondDependency = sinon.stub(new MockSecondDependency());
angular.mock.module(($provide): void => {
$provide.value('FirstDependency', mockFirstDependency);
$provide.value('SecondDependency', mockSecondDependency);
});
});
beforeEach(inject(
['TestedService', (_TestedService_: TestedService): void => {
TestedService = _TestedService_;
}]));
//Describe each method in the service
describe('method to test', (): void => {
it("should...", () => {
//testing goes in here
expect(TestedService.someMethod()).toBe("some value");
});
});
This is a simple example of how to test an angular service. In this case the service is called TestedService.
The first thing you'll see is that three variable declarations. The first two are declared to mock out the two dependencies of this service.(Assume this service has two dependencies). The last variable declaration is going to be assigned the actual service being tested.
Now in the beforeEach:
angular.mock.module
This line registers the module in which the service you are testing is in. This line is very important.
The next two line use Sinon.js to mock the dependencies of the service being tested. I recommend looking into Sinon.js
The way it works is we have a dependency called "FirstDependency" which I created a stub of and called "MockedFirstDependency" and here I created an instance of it.
Now for the next part which (the part that includes $provide)
$provide.value('FirstDependency', mockFirstDependency);
What the above line does is it tells Angular that every time the FirstDependency service is used, instead use mockFirstDependency.
Now in the next beforeEach all I do is inject the actual service which I am testing and assign it to my global variable.
Then let the testing begin
EDIT: Testing Controllers
describe('mainCtrl', (): void => {
var $controllerConstructor;
var MainCtrlInstance;
var mockScope;
var mockState;
var mockStates;
var mockGlobalData;
beforeEach(() => {
angular.mock.module('mainCtrlModule');
mockScope = sinon.stub(new MockScope());
mockState = sinon.stub(new MockState());
mockStates = sinon.stub(new MockState());
mockGlobalData = sinon.stub(new MockGlobalData());
inject(($controller: ng.IControllerService): void => {
$controllerConstructor = $controller;
});
//Constructs the controller, all dependencies must be injected here
MainCtrlInstance = $controllerConstructor('mainCtrl',
{
'$Scope': mockScope,
'$State': mockState,
'States': mockStates,
'srvGlobalData': mockGlobalData
}
);
});
describe('Method to Tests', (): void => {
it("should...", (): void => {
//Testing Begins
expect(MainCtrlInstance.method()).toBe("some value");
});
});
});
EDIT: Testing Directives
First off you will need to install Html2JsPreprocessor with this command: npm install karma-ng-html2js-preprocessor --save-dev as stated here.
karma.conf.js
files: [
//Obviously include all of your Angular files
//but make sure to include your jQuery before angular.js
"directory/to/html/directive.html", // include html for directive
"directive.js" // file directive is contained in
"directive.spec.js"" // spec file
]
// include the directive html file to be preprocessed
preprocessors: {
'directory/to/html/directive.html': 'ng-html2js'
},
plugins : [
'karma-chrome-launcher',
'karma-jasmine',
'karma-ng-html2js-preprocessor' //include as a plugin too
],
ngHtml2JsPreprocessor: {
//this part has a lot of useful features but unfortunately I
//never got them to work, Google if you need help
},
directive.js
export class myDirectiveController {
constructor(/*dependencies for controller*/) {
//initializations
}
//other methods for directive class
}
export class myDirective implements ng.IDirective {
constructor(/*dependencies for directive*/) { }
static instance(/*dependencies*/): ng.IDirective {
return new myDirective(/*dependencies for directive*/);
}
restrict = 'E';
templateUrl = 'myDirective.html';
controller = myDirectiveController;
controllerAs = 'myDirectiveController';
scope: {};
}
angular
.module('myDirectiveModule')
.directive('myDirective', myDirective.instance);
myDirective.spec.js
describe("myDirective", () => {
//do you variable declarations but I'm leaving them out for simplicity
beforeEach(() => {
angular.mock.module(
'myDirectiveModule', //and other modules in use
'directory/to/html/directive.html'
//include directive html as a module
)
// now do your mock dependencies as you did with services
mockDependency = sinon.stub(new MockDependency());
angular.mock.module(($provide): void => {
$provide.value('dependency', mockDependency);
}
//inject $compile and $rootScope
inject(($compile, $rootScope) => {
scope = $rootScope.$new();
// your directive gets compiled here
element = angular.element("<my-directive></my-directive>");
$compile(element)(scope);
$rootScope.$digest();
directiveController = element.controller('myDirective'); //this is your directive's name defined in .directive("myDirective", ...)
});
}
describe("simple test", () => {
it("should click a link", () => {
var a = element.find("a");
a.triggerHandler('click');
//very important to call scope.$digest every you change anything in the view or the model
scope.$digest();
expect('whatever').toBe('whatever');
});
});
}
Earlier when I stated to included your jQuery file before you Angular, do this because angular.element() will produce a jQuery object on which you can use the jQuery API, but if you do not include jQuery first then you angular.element() returns a jQLite object which contains less methods.
It is also important to call scope.$digest() because that updates the bindings for your directive.
Our Angular project moved to ES6 and Reduxjs, and now I am struggling to get controller unit tests working. Specifically, I cant seem to mock correctly when it comes to the class constructor. From what i have researched, i cant spyOn an ES6 class constructor, so i need to mock its dependencies and also accommodate the binding to lexical 'this' that ngRedux.connect() facilitates.
My test makes it to the connect function in the constructor, and then gives me the error: "'connect' is not a function"
I think i may have several things wrong here. If i comment out the connect line in the constructor, it'll get to my runOnLoad function and the error will tell me that fromMyActions isnt a function. this is because the redux connect function binds the actions to 'this', so given these issues, I take it I cant mock redux unless i provide its implementation. any advice? I am relatively new to angular as well - and my weakest area is unit testing and DI.
Here is my module and controller:
export const myControllerModule = angular.module('my-controller-module',[]);
export class MyController {
constructor($ngRedux, $scope) {
'ngInject';
this.ngRedux = $ngRedux;
const unsubscribe = this.ngRedux.connect(this.mapState.bind(this), myActions)(this);
$scope.$on('$destroy', unsubscribe);
this.runOnLoad();
}
mapState(state) {
return {};
}
runOnLoad() {
this.fromMyActions(this.prop);
}
}
myControllerModule
.controller(controllerId, MyController
.directive('myDirective', () => {
return {
restrict: 'E',
controllerAs: 'vm',
controller: controllerId,
templateUrl: htmltemplate
bindToController: true,
scope: {
data: '=',
person: '='
}
};
});
export default myControllerModule.name;
and my test:
import {myControllerModule,MyController} from './myController';
import 'angular-mocks/angular-mocks';
describe('test', () => {
let controller, scope;
beforeEach(function() {
let reduxFuncs = {
connect: function(){}
}
angular.mock.module('my-controller-module', function ($provide) {
$provide.constant('$ngRedux',reduxFuncs);
});
angular.mock.inject(function (_$ngRedux_, _$controller_, _$rootScope_) {
scope = _$rootScope_.$new();
redux = _$ngRedux_;
var scopeData = {
data : {"test":"stuff"},
person : {"name":"thatDude"}
} ;
scope.$digest();
controller = _$controller_(MyController, {
$scope: scope,
$ngRedux: redux
}, scopeData);
});
});
});
The idea behind Redux is that most of your controllers have no, or very little logic. The logic will be in action creators, reducers and selectors mostly.
In the example you provide, most of your code is just wiring things.
I personally don't test wiring, because it adds very little value, and those kinds of test are generally very brittle.
With that said, if you want to test your controllers nonetheless you have two options:
Use functions instead of classes for controllers. For most controllers using a class adds no real value. Instead use a function, and isolate the logic you want to test in another pure function. You can then test this function without even needing mocks etc.
If you still want to use classes, you will need to use a stub of ng-redux, (something like this: https://gist.github.com/wbuchwalter/d1448395f0dee9212b70 (it's in TypeScript))
And use it like this:
let myState = {
someProp: 'someValue'
}
let ngReduxStub;
let myController;
beforeEach(angular.mock.inject($injector => {
ngReduxStub = new NgReduxStub();
//how the state should be initially
ngReduxStub.push(myState);
myController = new MyController(ngReduxStub, $injector.get('someOtherService');
}));
Note: I personnaly don't use mapDispatchToProps (I expose my action creators through an angular service), so the stub does not handle actions, but it should be easy to add.
I have been getting the following error while trying to write tests using Karma and Jasmine:
TypeError: undefined is not a constructor (evaluating
$ngRedux.connect(function (state) { ({}, state.editAudience); }, _actions2.default)')
I managed to make it work by roughly translating wbuch's stub into es6 and using it like this:
angular.mock.module( $provide => {
ngReduxStub = new NgReduxStub()
ngReduxStub.push(myState)
const dependencies = ['$scope', '$ngRedux']
dependencies.forEach( dependency => {
if(dependency =='$ngRedux') { return $provide.value('$ngRedux', ngReduxStub)}
return $provide.value(dependency, {})
})
})
angular.mock.inject( ($compile, $rootScope, $componentController) => {
scope = $rootScope.$new()
ctrl = $componentController('csEditAudience', {$scope: scope}, {})
})
Here's my version of the stub
I hope this helps!
I'm using bindToController in a directive to have the isolated scope directly attached to the controller, like this:
app.directive('xx', function () {
return {
bindToController: true,
controller: 'xxCtrl',
scope: {
label: '#',
},
};
});
Then in the controller I have a default in case label is not specified in the HTML:
app.controller('xxCtrl', function () {
var ctrl = this;
ctrl.label = ctrl.label || 'default value';
});
How can I instantiate xxCtrl in the Jasmine unit tests so I can test the ctrl.label?
describe('buttons.RemoveButtonCtrl', function () {
var ctrl;
beforeEach(inject(function ($controller) {
// What do I do here to set ctrl.label BEFORE the controller runs?
ctrl = $controller('xxCtrl');
}));
it('should have a label', function () {
expect(ctrl.label).toBe('foo');
});
});
Check this to test the issue
In Angular 1.3 (see below for 1.4+)
Digging into the AngularJS source code I found an undocumented third argument to the $controller service called later (see $controller source).
If true, $controller() returns a Function with a property instance on which you can set properties.
When you're ready to instantiate the controller, call the function and it'll instantiate the controller with the properties available in the constructor.
Your example would work like this:
describe('buttons.RemoveButtonCtrl', function () {
var ctrlFn, ctrl, $scope;
beforeEach(inject(function ($rootScope, $controller) {
scope = $rootScope.$new();
ctrlFn = $controller('xxCtrl', {
$scope: scope,
}, true);
}));
it('should have a label', function () {
ctrlFn.instance.label = 'foo'; // set the value
// create controller instance
ctrl = ctrlFn();
// test
expect(ctrl.label).toBe('foo');
});
});
Here's an updated Plunker (had to upgrade Angular to make it work, it's 1.3.0-rc.4 now): http://plnkr.co/edit/tnLIyzZHKqPO6Tekd804?p=preview
Note that it's probably not recommended to use it, to quote from the Angular source code:
Instantiate controller later: This machinery is used to create an
instance of the object before calling the controller's constructor
itself.
This allows properties to be added to the controller before the
constructor is invoked. Primarily, this is used for isolate scope
bindings in $compile.
This feature is not intended for use by applications, and is thus not
documented publicly.
However the lack of a mechanism to test controllers with bindToController: true made me use it nevertheless.. maybe the Angular guys should consider making that flag public.
Under the hood it uses a temporary constructor, we could also write it ourselves I guess.
The advantage to your solution is that the constructor isn't invoked twice, which could cause problems if the properties don't have default values as in your example.
Angular 1.4+ (Update 2015-12-06):
The Angular team has added direct support for this in version 1.4.0. (See #9425)
You can just pass an object to the $controller function:
describe('buttons.RemoveButtonCtrl', function () {
var ctrl, $scope;
beforeEach(inject(function ($rootScope, $controller) {
scope = $rootScope.$new();
ctrl = $controller('xxCtrl', {
$scope: scope,
}, {
label: 'foo'
});
}));
it('should have a label', function () {
expect(ctrl.label).toBe('foo');
});
});
See also this blog post.
Unit Testing BindToController using ES6
If using ES6,you can import the controller directly and test without using angular mocks.
Directive:
import xxCtrl from './xxCtrl';
class xxDirective {
constructor() {
this.bindToController = true;
this.controller = xxCtrl;
this.scope = {
label: '#'
}
}
}
app.directive('xx', new xxDirective());
Controller:
class xxCtrl {
constructor() {
this.label = this.label || 'default value';
}
}
export default xxCtrl;
Controller Test:
import xxCtrl from '../xxCtrl';
describe('buttons.RemoveButtonCtrl', function () {
let ctrl;
beforeEach(() => {
xxCtrl.prototype.label = 'foo';
ctrl = new xxCtrl(stubScope);
});
it('should have a label', () => {
expect(ctrl.label).toBe('foo');
});
});
see this for more information:
Proper unit testing of
Angular JS
applications with ES6 modules
In my view, this controller is not meant to be tested in isolation, because it will never work in isolation:
app.controller('xxCtrl', function () {
var ctrl = this;
// where on earth ctrl.lable comes from???
ctrl.newLabel = ctrl.label || 'default value';
});
It is tightly coupled with the directive relying on receiving its scope properties. It is not re-usable. From looking at this controller, I have to wonder where this variable is coming from. It is no better than a leaky function internally using a variable from outside scope:
function Leaky () {
... many lines of code here ...
// if we are here we are too tired to notice the leakyVariable:
importantData = process(leakyVariable);
... mode code here ...
return unpredictableResult;
}
Now I have a leaky function whose behaviour is highly unpredictable based on the variable leakyVariable present (or not) in whatever scope the function is called.
Unsurprisingly this function is nightmare to test. Which is actually a good thing, perhaps to force the developer to rewrite the function into something more modular and re-usable. Which is not hard really:
function Modular (outsideVariable) {
... many lines of code here ...
// no need to hit our heads against the wall to wonder where the variable comes from:
importantData = process(outsideVariable);
... mode code here ...
return predictableResult;
}
No leaky issues and really easy to test and re-use. Which to me tells that using the good old $scope is a better way:
app.controller('xxCtrl', function ($scope) {
$scope.newLabel = $scope.label || 'default value';
});
Simple, short and easy to test. Plus no bulky directive object definition.
The original reasoning behind the controllerAs syntax was the leaky scope inherited from the parent. However, directive's isolated scope already solves this problem. Thus I don't see any reason to use the bulkier leaky syntax.
I've found a way that is not particulary elegant but works at least (if there's a better option leave a comment).
We set the value that "comes" from the directive, and then we call the controller function again to test whatever it does. I've made a helper "invokeController" to be more DRY.
For example:
describe('buttons.RemoveButtonCtrl', function () {
var ctrl, $scope;
beforeEach(inject(function ($rootScope, $controller) {
scope = $rootScope.$new();
ctrl = $controller('xxCtrl', {
$scope: scope,
});
}));
it('should have a label', function () {
ctrl.label = 'foo'; // set the value
// call the controller again with all the injected dependencies
invokeController(ctrl, {
$scope: scope,
});
// test whatever you want
expect(ctrl.label).toBe('foo');
});
});
beforeEach(inject(function ($injector) {
window.invokeController = function (ctrl, locals) {
locals = locals || {};
$injector.invoke(ctrl.constructor, ctrl, locals);
};
}));
I'm new to Angular and trying to figure out how to do things...
Using AngularJS, how can I inject a controller to be used within another controller?
I have the following snippet:
var app = angular.module("testApp", ['']);
app.controller('TestCtrl1', ['$scope', function ($scope) {
$scope.myMethod = function () {
console.log("TestCtrl1 - myMethod");
}
}]);
app.controller('TestCtrl2', ['$scope', 'TestCtrl1', function ($scope, TestCtrl1) {
TestCtrl1.myMethod();
}]);
When I execute this, I get the error:
Error: [$injector:unpr] Unknown provider: TestCtrl1Provider <- TestCtrl1
http://errors.angularjs.org/1.2.21/$injector/unpr?p0=TestCtrl1Provider%20%3C-%20TestCtrl1
Should I even be trying to use a controller inside of another controller, or should I make this a service?
If your intention is to get hold of already instantiated controller of another component and that if you are following component/directive based approach you can always require a controller (instance of a component) from a another component that follows a certain hierarchy.
For example:
//some container component that provides a wizard and transcludes the page components displayed in a wizard
myModule.component('wizardContainer', {
...,
controller : function WizardController() {
this.disableNext = function() {
//disable next step... some implementation to disable the next button hosted by the wizard
}
},
...
});
//some child component
myModule.component('onboardingStep', {
...,
controller : function OnboadingStepController(){
this.$onInit = function() {
//.... you can access this.container.disableNext() function
}
this.onChange = function(val) {
//..say some value has been changed and it is not valid i do not want wizard to enable next button so i call container's disable method i.e
if(notIsValid(val)){
this.container.disableNext();
}
}
},
...,
require : {
container: '^^wizardContainer' //Require a wizard component's controller which exist in its parent hierarchy.
},
...
});
Now the usage of these above components might be something like this:
<wizard-container ....>
<!--some stuff-->
...
<!-- some where there is this page that displays initial step via child component -->
<on-boarding-step ...>
<!--- some stuff-->
</on-boarding-step>
...
<!--some stuff-->
</wizard-container>
There are many ways you can set up require.
(no prefix) - Locate the required controller on the current element. Throw an error if not found.
? - Attempt to locate the required controller or pass null to the link fn if not found.
^ - Locate the required controller by searching the element and its parents. Throw an error if not found.
^^ - Locate the required controller by searching the element's parents. Throw an error if not found.
?^ - Attempt to locate the required controller by searching the element and its parents or pass null to the link fn if not found.
?^^ - Attempt to locate the required controller by searching the element's parents, or pass null to the link fn if not found.
Old Answer:
You need to inject $controller service to instantiate a controller inside another controller. But be aware that this might lead to some design issues. You could always create reusable services that follows Single Responsibility and inject them in the controllers as you need.
Example:
app.controller('TestCtrl2', ['$scope', '$controller', function ($scope, $controller) {
var testCtrl1ViewModel = $scope.$new(); //You need to supply a scope while instantiating.
//Provide the scope, you can also do $scope.$new(true) in order to create an isolated scope.
//In this case it is the child scope of this scope.
$controller('TestCtrl1',{$scope : testCtrl1ViewModel });
testCtrl1ViewModel.myMethod(); //And call the method on the newScope.
}]);
In any case you cannot call TestCtrl1.myMethod() because you have attached the method on the $scope and not on the controller instance.
If you are sharing the controller, then it would always be better to do:-
.controller('TestCtrl1', ['$log', function ($log) {
this.myMethod = function () {
$log.debug("TestCtrl1 - myMethod");
}
}]);
and while consuming do:
.controller('TestCtrl2', ['$scope', '$controller', function ($scope, $controller) {
var testCtrl1ViewModel = $controller('TestCtrl1');
testCtrl1ViewModel.myMethod();
}]);
In the first case really the $scope is your view model, and in the second case it the controller instance itself.
I'd suggest the question you should be asking is how to inject services into controllers. Fat services with skinny controllers is a good rule of thumb, aka just use controllers to glue your service/factory (with the business logic) into your views.
Controllers get garbage collected on route changes, so for example, if you use controllers to hold business logic that renders a value, your going to lose state on two pages if the app user clicks the browser back button.
var app = angular.module("testApp", ['']);
app.factory('methodFactory', function () {
return { myMethod: function () {
console.log("methodFactory - myMethod");
};
};
app.controller('TestCtrl1', ['$scope', 'methodFactory', function ($scope,methodFactory) { //Comma was missing here.Now it is corrected.
$scope.mymethod1 = methodFactory.myMethod();
}]);
app.controller('TestCtrl2', ['$scope', 'methodFactory', function ($scope, methodFactory) {
$scope.mymethod2 = methodFactory.myMethod();
}]);
Here is a working demo of factory injected into two controllers
Also, I'd suggest having a read of this tutorial on services/factories.
There is no need to import/Inject your controller in JS. You can just inject your controller/nested controller through your HTML.It's worked for me.
Like :
<div ng-controller="TestCtrl1">
<div ng-controller="TestCtrl2">
<!-- your code-->
</div>
</div>
you can also use $rootScope to call a function/method of 1st controller from second controller like this,
.controller('ctrl1', function($rootScope, $scope) {
$rootScope.methodOf2ndCtrl();
//Your code here.
})
.controller('ctrl2', function($rootScope, $scope) {
$rootScope.methodOf2ndCtrl = function() {
//Your code here.
}
})
<div ng-controller="TestCtrl1">
<div ng-controller="TestCtrl2">
<!-- your code-->
</div>
</div>
This works best in my case, where TestCtrl2 has it's own directives.
var testCtrl2 = $controller('TestCtrl2')
This gives me an error saying scopeProvider injection error.
var testCtrl1ViewModel = $scope.$new();
$controller('TestCtrl1',{$scope : testCtrl1ViewModel });
testCtrl1ViewModel.myMethod();
This doesn't really work if you have directives in 'TestCtrl1', that directive actually have a different scope from this one created here.
You end up with two instances of 'TestCtrl1'.
The best solution:-
angular.module("myapp").controller("frstCtrl",function($scope){
$scope.name="Atul Singh";
})
.controller("secondCtrl",function($scope){
angular.extend(this, $controller('frstCtrl', {$scope:$scope}));
console.log($scope);
})
// Here you got the first controller call without executing it
use typescript for your coding, because it's object oriented, strictly typed and easy to maintain the code ...
for more info about typescipt click here
Here one simple example I have created to share data between two controller using Typescript...
module Demo {
//create only one module for single Applicaiton
angular.module('app', []);
//Create a searvie to share the data
export class CommonService {
sharedData: any;
constructor() {
this.sharedData = "send this data to Controller";
}
}
//add Service to module app
angular.module('app').service('CommonService', CommonService);
//Create One controller for one purpose
export class FirstController {
dataInCtrl1: any;
//Don't forget to inject service to access data from service
static $inject = ['CommonService']
constructor(private commonService: CommonService) { }
public getDataFromService() {
this.dataInCtrl1 = this.commonService.sharedData;
}
}
//add controller to module app
angular.module('app').controller('FirstController', FirstController);
export class SecondController {
dataInCtrl2: any;
static $inject = ['CommonService']
constructor(private commonService: CommonService) { }
public getDataFromService() {
this.dataInCtrl2 = this.commonService.sharedData;
}
}
angular.module('app').controller('SecondController', SecondController);
}
I want to test an AngularJS directive declared like this
app.directive('myCustomer', function() {
return {
template: 'cust.html'
controller: 'customerController'
};
});
In the test I would like to inject (or override) the controller, so that I can test just the other parts of the directive (e.g. the template). The customerController can of course be tested separately. This way I get a clean separation of tests.
I have tried overriding the controller by setting the controller property in the test.
I have tried injecting the customController using $provide.
I have tried setting ng-controller on the html directive declaration used in the test.
I couldn't get any of those to work. The problem seems to be that I cannot get a reference to the directive until I have $compiled it. But after compilation, the controller is already set up.
var element = $compile("<my-customer></my-customer>")($rootScope);
One way is to define a new module (e.g. 'specApp') that declares your app (e.g. 'myApp') as a dependency. Then register a 'customerController' controller with the 'specApp' module. This will effectively "hide" the customerController of 'myApp' and supply this mock-controller to the directive when compiled.
E.g.:
Your app:
var app = angular.module('myApp', []);
...
app.controller('customerController', function ($scope,...) {
$scope = {...};
...
});
app.directive('myCustomer', function () {
return {
template: 'cust.html',
controller: 'customerController'
};
});
Your spec:
describe('"myCustomer" directive', function () {
$compile;
$newScope;
angular.module('specApp', ['myApp'])
/* Register a "new" customerController, which will "hide" that of 'myApp' */
.controller('customerController', function ($scope,...) {
$scope = {...};
...
});
beforeEach(module('specApp'));
it('should do cool stuff', function () {
var elem = angular.element('<div my-customer></div>');
$compile(elem)($newScope);
$newScope.$digest();
expect(...
});
});
See, also, this working demo.
I think there is a simpler way than the accepted answer, which doesn't require creating a new module.
You were close when trying $provide, but for mocking controllers, you use something different: $controllerProvider. Use the register() method in your spec to mock out your controller.
beforeEach(module('myApp', function($controllerProvider) {
$controllerProvider.register('customerContoller', function($scope) {
// Controller Mock
});
});