I have a component like follows and would like to test what the $onChange
method does in case the binding myBinding
changes.
I tried the whole morning, but could not find a way to solve this.
angular
.module('project.myComponent', [])
.component('myComponent', {
bindings: {
myBinding: '<'
},
template: '<div>{{$ctrl.result}}</div>',
controller: myComponentController
});
function myComponentController($filter, someService) {
var ctrl = this;
ctrl.result = 0;
$ctrl.$onChange = function (changes) {
if(angular.isDefined(changes.myBinding)) {
if(angular.isDefined(changes.myBinding.currentValue)) {
if(angular.isDefined(changes.myBinding.currentValue != changes.myBinding.previousValue)) {
myService.doSomething(changes.myBinding.currentValue).then(
function(data) {
ctrl.result = changes.myBinding.currentValue * 3;
}
);
}
}
}
}
}
I would like my test acting like it is the components parent which changes the value of the binding.
require('angular-mocks');
describe('myComponment', function() {
var element, scope;
beforeEach(inject(function(_$rootScope_, _$compile_) {
}));
fit('should display the controller defined title', function() {
// prepare test and set myBinding to 10
expect(component.result).toBe(30);
});
});
Is that possible? How? Any hints? Plunker, CodePen or other examples?
For click event we can use triggerEventHandler method of Angular DebugElement class. We can also call native JavaScript click method of button. On click of button, we call a component method and it is possible that our component method has other dependencies to execute.
TestBed is the primary api for writing unit tests for Angular applications and libraries.
The ComponentFixture is a test harness for interacting with the created component and its corresponding element. Access the component instance through the fixture and confirm it exists with a Jasmine expectation: content_copy const component = fixture. componentInstance; expect(component).
Which of the following TestBed method is used to create an Angular component under test? The correct answer is - createComponent!
Testing AngularJS components doesn't differ much from testing directives.
To test controller's methods / properties, you can access the instance of the component's controller using element.controller("componentName")
method (componentName
- is a camelCase directive / component name).
Here is example using $compile
service to test the component and $onChanges
hook:
angular.module('myApp', [])
.component('myComponent', {
bindings: {
myBinding: '<'
},
template: '<div>{{$ctrl.result}}</div>',
controller: 'myComponentController'
})
.controller('myComponentController', ['$filter', 'myService', function myComponentController($filter, myService) {
var ctrl = this;
ctrl.$onInit = onInit;
ctrl.$onChanges = onChanges;
function onInit() {
ctrl.result = ctrl.myBinding;
}
function onChanges(changes) {
if (angular.isDefined(changes.myBinding)) {
if (angular.isDefined(changes.myBinding.currentValue)) {
if (!angular.equals(changes.myBinding.currentValue, changes.myBinding.previousValue)) {
myService.doSomething(changes.myBinding.currentValue).then(
function (data) {
ctrl.result = data;
}
);
}
}
}
}
}])
.service('myService', ['$timeout', function ($timeout) {
return {
doSomething: function (x) {
return $timeout(function () {
return x * 3;
}, 500);
}
};
}]);
/*
TEST GO HERE
*/
describe('Testing a component controller', function() {
var $scope, ctrl, $timeout, myService;
beforeEach(module('myApp', function ($provide) {
}));
beforeEach(inject(function ($injector) {
myService = $injector.get('myService');
$timeout = $injector.get('$timeout');
}));
describe('with $compile', function () {
var element;
var scope;
var controller;
beforeEach(inject(function ($rootScope, $compile) {
scope = $rootScope.$new();
scope.myBinding = 10;
element = angular.element('<my-component my-binding="myBinding"></my-component>');
element = $compile(element)(scope);
controller = element.controller('myComponent');
scope.$apply();
}));
it('should render template', function () {
expect(element[0].innerText).toBe('10'); //initial
$timeout.flush(); //onchanges happened and promise resolved from the service
//undefined -> 10
expect(element[0].innerText).toBe('30');
});
it('should reflect to changes', function () {
spyOn(myService, "doSomething").and.callThrough();
scope.myBinding = 15; //change the binding
scope.$apply(); //we need to call $apply to pass the changes down to the component
$timeout.flush();
expect(myService.doSomething).toHaveBeenCalled(); // check if service method was called
expect(controller.result).toBe(45); // check controller's result value
});
})
});
.as-console-wrapper {
height:0;
}
<!DOCTYPE html>
<html>
<head>
<!-- jasmine -->
<script src="//cdnjs.cloudflare.com/ajax/libs/jasmine/2.8.0/jasmine.js"></script>
<!-- jasmine's html reporting code and css -->
<script src="//cdnjs.cloudflare.com/ajax/libs/jasmine/2.8.0/jasmine-html.js"></script>
<link href="//cdnjs.cloudflare.com/ajax/libs/jasmine/2.8.0/jasmine.css" rel="stylesheet" />
<script src="//cdnjs.cloudflare.com/ajax/libs/jasmine/2.8.0/boot.js"></script>
<!-- angular itself -->
<script src="//ajax.googleapis.com/ajax/libs/angularjs/1.6.4/angular.js"></script>
<!-- angular's testing helpers -->
<script src="//ajax.googleapis.com/ajax/libs/angularjs/1.6.4/angular-mocks.js"></script>
</head>
<body>
<!-- bootstrap jasmine! -->
<script>
var jasmineEnv = jasmine.getEnv();
// Tell it to add an Html Reporter
// this will add detailed HTML-formatted results
// for each spec ran.
jasmineEnv.addReporter(new jasmine.HtmlReporter());
// Execute the tests!
jasmineEnv.execute();
</script>
</body>
</html>
You can also test your components using $componentController
service. But in this case you will need to explicitly call life-cycle hooks in your tests, like:
ctrl = $componentController('myComponent', {$scope: scope}, { myBinding: 10 });
ctrl.$onInit();
To test $onChanges
hook, you will need to pass a "properly" constructed changes object as argument:
angular.module('myApp', [])
.component('myComponent', {
bindings: {
myBinding: '<'
},
template: '<div>{{$ctrl.result}}</div>',
controller: 'myComponentController'
})
.controller('myComponentController', ['$filter', 'myService', function myComponentController($filter, myService) {
var ctrl = this;
ctrl.$onInit = onInit;
ctrl.$onChanges = onChanges;
function onInit() {
ctrl.result = ctrl.myBinding;
}
function onChanges(changes) {
if (angular.isDefined(changes.myBinding)) {
if (angular.isDefined(changes.myBinding.currentValue)) {
if (!angular.equals(changes.myBinding.currentValue, changes.myBinding.previousValue)) {
myService.doSomething(changes.myBinding.currentValue).then(
function (data) {
ctrl.result = data;
}
);
}
}
}
}
}])
.service('myService', ['$timeout', function ($timeout) {
return {
doSomething: function (x) {
return $timeout(function () {
return x * 3;
}, 500);
}
};
}]);
/*
TEST GO HERE
*/
describe('Testing a component controller', function () {
var $scope, ctrl, $timeout, myService;
beforeEach(module('myApp', function ($provide) {
}));
beforeEach(inject(function ($injector) {
myService = $injector.get('myService');
$timeout = $injector.get('$timeout');
}));
describe('with $componentController', function () {
var scope;
var controller;
beforeEach(inject(function ($rootScope, $componentController) {
scope = $rootScope.$new();
scope.myBinding = 10;
controller = $componentController('myComponent', {$scope: scope}, {myBinding: 10});
controller.$onInit();
}));
it('should reflect to changes', function () {
spyOn(myService, "doSomething").and.callThrough();
controller.$onChanges({myBinding: {currentValue: 15, previousValue: 10}});
$timeout.flush(); // resolve service promise
expect(myService.doSomething).toHaveBeenCalled(); // check if service method was called
expect(controller.result).toBe(45); // check controller's result value
});
})
});
.as-console-wrapper {
height:0;
}
<!DOCTYPE html>
<html>
<head>
<!-- jasmine -->
<script src="//cdnjs.cloudflare.com/ajax/libs/jasmine/2.8.0/jasmine.js"></script>
<!-- jasmine's html reporting code and css -->
<script src="//cdnjs.cloudflare.com/ajax/libs/jasmine/2.8.0/jasmine-html.js"></script>
<link href="//cdnjs.cloudflare.com/ajax/libs/jasmine/2.8.0/jasmine.css" rel="stylesheet" />
<script src="//cdnjs.cloudflare.com/ajax/libs/jasmine/2.8.0/boot.js"></script>
<!-- angular itself -->
<script src="//ajax.googleapis.com/ajax/libs/angularjs/1.6.4/angular.js"></script>
<!-- angular's testing helpers -->
<script src="//ajax.googleapis.com/ajax/libs/angularjs/1.6.4/angular-mocks.js"></script>
</head>
<body>
<!-- bootstrap jasmine! -->
<script>
var jasmineEnv = jasmine.getEnv();
// Tell it to add an Html Reporter
// this will add detailed HTML-formatted results
// for each spec ran.
jasmineEnv.addReporter(new jasmine.HtmlReporter());
// Execute the tests!
jasmineEnv.execute();
</script>
</body>
</html>
P.S.: $onChange
is not a valid name of the component's life-cycle hook. It should be $onChanges
.
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