test file:
describe('$rootScope', function() {
describe('$on', function() {
var credentials = "Basic abcd1234";
var $scope;
var $rootScope;
var $httpBackend;
...
beforeEach(inject(function($injector, $controller, _$rootScope_, $state, _$q_, currentUserService) {
$scope = _$rootScope_.$new();
$rootScope = _$rootScope_;
$httpBackend.when('GET', 'dist/app/login/login.html').respond({'title': 'TEST_TITLE'}, {'A-Token': 'xxx'});
}));
...
it('should set $scope.title if noAuthorization', function() {
spyOn($rootScope, '$on');
$controller('AppCtrl', {$scope: $scope});
$rootScope.$broadcast("$stateChangeStart");
$rootScope.$apply();
expect($rootScope.$on).toHaveBeenCalled();
expect($scope.title).toBe('TEST_TITLE');
});
});
});
$scope.title is always undefined in my tests. the expect always fails. I've tried $emit, $apply, etc. This is within a controller, inside of a $rootScope.on method.
But if I console log $scope.title inside of the js file, it does show that $scope.title has bene updated.
I should also mention that the function being called is not in $scope, ie it is not $scope.updateTitle, it is just function updateTitle(...)
I don't feel the actual code is necessary to show because it does it's job. I am just wondering why the $scope in the tests is not getting updated.
In a nutshell: don't forget .andCallThrough() on the spy. The Jasmine spy needed andCallThrough(). Use element.scope() to access the correct scope.
spyOn(scope, '$apply').andCallThrough();
Related
My controller doesnt do a lot other than call methods in a service, the service wraps up and returns its functions, I have already written unit tests for the service mocking the http request.
Is it even worth unit testing the controller in this instance and if so what would I be testing as I have already tested the service functionality.
Below is my controller:
'use strict';
/* Controllers */
var calculatorControllers = angular.module('calculatorControllers', []);
calculatorControllers.controller('BodyController', ['$scope',
function($scope) {
$scope.toggleNavBarActive = function($event) {
$($event.currentTarget).parent().find('.active').removeClass('active');
$($event.currentTarget).addClass('active');
};
}]);
calculatorControllers.controller('CalculatorCtrl', ['$scope', 'CalculatorService',
function($scope, CalculatorService) {
$scope.orderProp = 'asc';
$scope.result = ' awaiting calculation';
$scope.sum = {};
$scope.add = function(val1, val2) {
var promise = CalculatorService.add(val1, val2);
promise.then(function(response) {
$scope.result = response;
});
};
}]);
calculatorControllers.controller('AboutCtrl', ['$scope', '$routeParams',
function($scope, $routeParams) {
}]);
Is it even worth unit testing the controller in this instance
Yes, you should aim for 100% coverage, not matter controller or service. I would test two things here (Jasmine):
it('inits $scope', function() {
var $scope = {};
$controller('PasswordController', { $scope: $scope });
expect($scope.orderProp).toEqual('asc');
expect($scope.result).toEqual(' awaiting calculation');
expect($scope.sum).toEqual({});
});
it('calls CalculatorService and sets the result', function() {
var $scope = {};
$controller('PasswordController', { $scope: $scope });
$scope.sum(1, 2);
expect(CalculatorServiceMock).toHaveBeenCalledWith(1, 2);
resolveCalculatorServiceMockAddSpyWith(3);
expect($scope.result).toEqual(3);
});
The only case when the controller methods don't require testing is
$scope.calculator = CalculatorService;
So all view calls like {{ calculator.sum(...) }} are done by the service.
In every other case controller methods should be tested. Since CalculatorService unit was already tested, it has to be mocked in order for controller logic to be tested in isolation.
I am unit testing an AngularJS directive with Jasmine.
I am getting this error even though I injected $compile in a beforeEach statement:
Reference Error: can't find variable: $compile
describe('test', function() {
beforeEach(inject(function(_$compile_, _$rootScope_) {
$compile = _$compile_;
$rootScope = _$rootScope_;
}));
describe('testCase', function() {
var nlElement = angular.element('<div directive></div>');
var element = $compile(nlElement)($rootScope); // this is where the error is being thrown
$rootScope.$digest();
it(...)
});
});
Do I have to include the statements in the second describe in the it blocks? Ultimately I want to be able to inject all three of those statements before each test, but I am trying to resolve the $compile error at the moment.
It turns out that the describe blocks are executed before the beforeEach statements, which is counter-intuitive to me. Also, if you want to initialize variables and your directive before your tests (like I did in the second describe block, then include it in a beforeEach statement, and test your assertions in it blocks.
describe('test', function() {
describe('testCase', function() {
beforeEach(inject(function(_$compile_, _$rootScope_) {
$compile = _$compile_;
$rootScope = _$rootScope_;
}));
beforeEach(inject(function() {
var nlElement = angular.element('<div directive></div>');
var element = $compile(nlElement)($rootScope); // this is where the error is being thrown
$rootScope.$digest();
}));
it(...)
});
});
I would like to know how to test some Angular scope variables at my controller that was created inside an ajax request.
What I mean is... This is my controller:
app.controller('NewQuestionCtrl', function ($scope, Question) {
loadJsonAndSetScopeVariables($scope, Question);
});
function loadJsonAndSetScopeVariables(scope, Question) {
Question.loadJson().then(function(success) {
var result = success.data.variables;
scope.levels = result.levels;
scope.tags = result.tags;
scope.difficulties = result.difficulties;
scope.questionTypes = result.questionTypes;
scope.areas = result.areas;
},function(data){
});
}
One of the prerequisites is not to use mock.
At my test I was able to inject my Question service:
describe('Controller: NewQuestionCtrl', function () {
beforeEach(angular.mock.module('testmeApp'));
var NewQuestionCtrl, scope, QuestionService;
beforeEach(inject(function ($controller, $rootScope, Question) {
scope = $rootScope.$new();
QuestionService = Question;
NewQuestionCtrl = $controller('NewQuestionCtrl', {
$scope: scope
});
}));
it('should attach a list of areas to the scope', function (done) {
expect(scope.areas).toBeDefined();
done();
});
Please, someone could help me?
Create a mock for Question and use that. There are several ways to do this. This is just one of them.
You could alternatively inject a real instance of Question and spy on that instead, but a mock is preferred to isolate these unit tests from the Question unit tests.
var questionDeferred, myController, scope;
var mockQuestion = {
loadJson: angular.noop
};
beforeEach(inject(function($q, $rootScope, $controller) {
questionDeferred = $q.defer();
scope = $rootScope.$new();
spyOn(mockQuestion, 'loadJson').and.returnValue(questionDeferred);
// Because your function is run straight away, you'll need to create
// your controller in this way in order to spy on Question.loadJson()
myController = $controller('NewQuestionCtrl', {
$scope: scope,
Question: mockQuestion
});
}));
it('should attach a list of areas to the scope', function (done) {
questionDeferred.resolve({/*some data*/});
scope.$digest();
expect(scope.areas).toBeDefined();
done();
});
I'm kinda new in AngularJS unit testing and I'm having some troubles to test a controller method that was written in a directive.
This is my directive.js
app.directive('formLogin', ['AuthService', function(AuthService){
return {
restrict: 'E',
templateUrl: utils.baseUrl + 'partials/_home-form-login.html',
replace: true,
controller: function ($scope, $element, $http, $location) {
$scope.visible = false;
$scope.showForm = function () {
$scope.visible = !$scope.visible;
};
}
};
}]);
And here goes my unit-test.js
describe('formLogin ctrl', function () {
var element, scope, compile;
beforeEach(module('Application'));
beforeEach(inject(function ($rootScope, $compile) {
element = angular.element('<form-login></form-login>');
scope = $rootScope.$new();
compile = $compile(element)($scope);
}));
it('Test', function() {
expect(scope.visible).toBe(false);
})
});
And by doing this the "scope.visible" come as undefined.
There are some way to take the $scope from my controller that assumes in "scope" variable the "visible" property and the "showForm" method?
From this link
it looks like you might need to do a scope.$digest();
You appear to have a couple problems:
compile = $compile(element)($scope); -- here, $scope is undefined. It should be compile = $compile(element)(scope);
As mentioned by smk, you need to digest your scope to finish the directive creation process
This is especially important because you are using templateUrl. When you just use a locally-defined template, as Krzysztof does in his example, you can get by with skipping this step.
You will probably notice that when you add scope.$digest() you will get a different problem about an unexpected GET request. This is because Angular is trying to GET the templateUrl, and during testing all HTTP requests must be configured / expected manually. You might be tempted to inject $httpBackend and do something like $httpBackend.whenGet(/partials\//).respond('<div/>'); but you will end up with problems that way.
The better way to accomplish this is to inject the template $templateCache -- Karma has a pre-processor to do this for you, or you can do it manually. There have been other StackOverflow questions you can read about this, like this one.
I've modified your example to manually insert a simple template into the $templateCache as a simple example in this plunkr.
You should take a look into Karma's html2js pre-processor to see if it can do the job for you.
If your directive hasn't isolated scope, you can call methods from directive controller and test how it impacts to scope values.
describe('myApp', function () {
var scope
, element
;
beforeEach(function () {
module('myApp');
});
describe('Directive: formLogin', function () {
beforeEach(inject(function ($rootScope, $compile) {
scope = $rootScope.$new();
element = angular.element('<form-login></form-login>');
$compile(element)(scope);
}));
it('.showForm() – changes value of $scope.visible', function() {
expect(scope.visible).toBe(false);
scope.showForm();
expect(scope.visible).toBe(true);
});
});
});
jsfiddle: http://jsfiddle.net/krzysztof_safjanowski/L2rBV/1/
We have a directive that has an optional attribute on it. If the attribute is not there, it provides a default value. The attribute is usually set from data in the scope (i.e., the value of the attribute is usually an expression and not a literal string. See http://jsfiddle.net/8PGZ4/
As such, we are using attrs.$observe in the directive to set up the scope properly. This works great in the app itself. However, when trying to test this (using Jasmine), the function in the $observe never gets run. Our test looks something like this:
describe("myDirective", function(){
function getDirectiveScope(compile, rootScope, directiveHTML)
{
return (compile(directiveHTML)(rootScope)).scope();
}
describe("foo", function () {
it("should return the default value", inject(function ($compile, $rootScope) {
var directiveScope = getDirectiveScope($compile, $rootScope, '<div my-directive></div>');
expect(directiveScope.bar).toBe("No Value");
}));
it("should now return the value given", inject(function ($compile, $rootScope) {
$rootScope.foo = "asdf";
var directiveScope = getDirectiveScope($compile, $rootScope, '<div my-directive foo="{{foo}}"></div>');
expect(directiveScope.bar).toBe("asdf");
}));
});
});
These then fail with the following errors:
Expected undefined to be 'No Value'.
Expected undefined to be 'asdf'.
I put a console.log in the $observe function to try to see what was going on, and when the tests run, I never see the log. Is there an explicit call I have to make for the $observe to run? Is there something else going on here?
You need to trigger a digest cycle to let the AngularJS magic happen in your test. Try:
it("should return the default value", inject(function ($compile, $rootScope) {
var directiveScope = getDirectiveScope($compile, $rootScope, '<div my-directive></div>');
$rootScope.$digest();
expect(directiveScope.bar).toBe("No Value");
}));