I have a factory defined like this:
angular.module("myServices")
.factory("$service1", ["$rootScope", "$service2", function($rootScope, $service2){...})];
Now, I want to test it, but just injecting $service1 is not working because i get an 'unknown provider' error. So I tried something like that. But I still can't make it work. Why?
beforeEach(function() {
module("myServices");
inject(function ($injector) {
dependencies["$service2"] = $injector.get("$service2");
});
module(function($provide) {
$provide.value("$service1", dependencies["$service2"]);
});
inject(function($injector) {
factory = $injector.get("$service1");
});
});
This is what's working in my tests, using underscores:
describe('Service: $service1', function () {
var $service2, scope;
beforeEach(inject(function (_$service2_, $rootScope) {
$service2 = _$service2_;
scope = $rootScope;
}));
//tests
});
If that still doesn't work, then maybe you're not loading the relevant files (such as service2.js) in your tests.
Related
I'm trying to write a test for a controller that has $rootScope.$on('accountsSet', function (event).... So in the tests I'm using .broadcast.andCallThrough() which many other questions here in SO suggest while it also worked before for me.
So my controller is pretty simple:
angular.module('controller.sidemenu', [])
.controller('SidemenuCtrl', function($rootScope, $scope, AccountsService) {
$rootScope.$on('accountsSet', function (event) {
$scope.accounts = AccountsService.getAccounts();
$scope.pro = AccountsService.getPro();
});
});
Any the test is simple as well:
describe("Testing the SidemenuCtrl.", function () {
var scope, createController, accountsService;
beforeEach(function(){
angular.mock.module('trevor');
angular.mock.module('templates');
inject(function ($injector, AccountsService) {
scope = $injector.get('$rootScope');
controller = $injector.get('$controller');
accountsService = AccountsService;
createController = function() {
return controller('SidemenuCtrl', {
'$scope' : $injector.get('$rootScope'),
'AccountsService' : accountsService,
});
};
});
});
it("Should load the SidemenuCtrl.", function () {
accountsService.setPro(true);
spyOn(scope, '$broadcast').andCallThrough();
var controller = createController();
scope.$broadcast("accountsSet", true);
expect(scope.pro).toBeTruthy();
});
});
The error I'm getting if for spyOn(scope, '$broadcast').andCallThrough();. Note that scope for this tests is rootScope so that shouldn't be a problem.
So the error that refers to that line:
TypeError: 'undefined' is not a function (evaluating 'spyOn(scope, '$broadcast').andCallThrough()')
at .../tests/controllers/sidemenu.js:30
I'm turning my comment into an answer since it turned out to be the solution:
In jasmine 2.0 the syntax of spies has changed (and many other things, see the beautiful docs here)
the new syntax is
spyOn(foo, 'getBar').and.callThrough();
Compare with the jasmine 1.3 syntax of:
spyOn(foo, 'getBar').andCallThrough();
I've just read a lot of articles about mocking $http and something is wrong with my code. I still have error: No pending request to flush !
My method from controllers.js looks similar to this (browserDebugMode, webRoot, commentsAction are global variables - it wasn't mi idea to make it global :D)
$scope.getComments = function(){
if (browserDebugMode) {
$http({
method : "GET",
url : webRoot+commentsAction,
params : {action: "list"},
})
.success(function(data, status) {
//...
})
.error(function(data, status) {
//...
});
}
}
And now test for it:
var browserDebugMode = true;
var webRoot = "http://localhost/name";
var commentsAction = '/commentsMobile.php';
describe('myApp', function() {
var scope,
httpBackend,
http,
controller;
beforeEach(angular.mock.module('myApp'));
describe('NewsDetailCtrl', function() {
beforeEach(inject(function ($rootScope, $controller, $httpBackend, $http) {
scope = $rootScope.$new();
httpBackend = $httpBackend;
http = $http;
httpBackend.when("GET", webRoot+commentsAction).respond([{}]);
controller = $controller('NewsDetailCtrl', {
'$scope': scope, 'GlobalService': globalService, $http: $http
});
}));
it('checks if AJAX is done', function () {
httpBackend.expectGET(webRoot+commentsAction).respond([{}]);
scope.getComments()
httpBackend.flush();
});
});
});
And please don't ask for PHP script :) I was pushed to do it.
I just want to check if I can test $http, nothing more. I don't know what I do wrong. I tested other things in that controller and it was okay, I looked if getComments() is fired with console.log and it's fired. Something must be wrong with configuring it.
Your code under test and the unit tests execute in different contexts, so they will have different global objects and therefore the browserDebugMode that exists in your tests is different to the one in your actual code.
The controller should inject $window (Angular's wrapper around the window object) and then check the browserDebugMode property of that:
if ($window.browserDebugMode) {
// actual code
}
The tests should also inject $window and then set the browserDebugMode property of that:
beforeEach(inject(function ($window) {
$window.browserDebugMode = true;
}));
Now both the controller and tests will reference the same global object, the if condition should evaluate true, and the $http call should execute.
My Angular script looks like this:
var app = angular.module("myApp", []);
app.controller('TestController', function ($scope) {
$scope.test= "TEST";
});
My test file looks like this:
describe('first test', function() {
var $scope;
beforeEach(function (){
module('myApp');
inject(function($rootScope, $controller) {
$scope = $rootScope.$new();
ctrl = $controller('TestController', {
$scope: $scope
});
});
it('scope should have test', function() {
expect($scope.test).toEqual("TEST");
});
});
This test fails saying $scope.test is undefined. I debugged in Chrome and saw that $scope has a bunch of properties on it, but none of them are test. I've looked through several examples online, and they all look pretty similar to this. i'm not quite sure what i'm doing wrong here and i'm stuck....
edit
I tried adding $controller to inject, but i'm still having the same problem.
You need to pass $controller service alongside with $rootScope:
inject(function($rootScope, $controller) {
$scope = $rootScope.$new();
ctrl = $controller('TestController', {
$scope: $scope
});
});
I'm getting this error. It's something along the lines of me injector being unable to resolve a required dependency, but even with my limited knowledge of angular I'm pretty sure this code shouldn't be depending on any modules.
This code works perfectly fine in the browser, however it doesn't seem to want to work in my test. I've been following the examples from the documentation
My angular version is 1.2.13 (edit: now using 1.12.15).
Here's my code:
var app = angular.module('app', [])
.controller('GreetingCtrl', function ($scope) {
$scope.title = "Hello World!";
$scope.message = "Test, test. One? Two?";
});
Here's the jasmine test that's failing.
describe('app controllers', function () {
beforeEach(module('app'));
describe('GreetingCtrl', function () {
it('should says hello world', inject(function ($controller) {
var $scope = {};
$controller('GreetingCtrl', $scope);
expect($scope.title).toBe("Hello World!");
}));
});
});
I don't believe it's even gotten to the point of running my test because it fails before even running it. I believe I've correctly concatenated the files correctly as well. Here's the error I've received from the jasmine test runner.
Error: [$injector:unpr] http://errors.angularjs.org/1.2.13/$injector/unpr?p0=%24scopeProvider%20%3C-%20%24scope (line 4569) (1)
Edit: tried upgrading to 1.12.15, nothing has changed.
So apparently after a little chat on the IRC, the documentation might be out of date, & this is a working solution. I was linked to this solution, & corrected my test accordingly.
describe('app controllers', function () {
var ctrl, scope;
beforeEach(module('app'));
describe('GreetingCtrl', function () {
beforeEach(inject(function ($rootScope, $controller) {
scope = $rootScope.$new();
ctrl = $controller('GreetingCtrl', {$scope: scope});
}));
it('should says hello world', function () {
expect(scope.title).toBe("Hello World!");
});
});
});
Edit:
I accidentally misread the docs originally & here's a cleaner solution closer to the docs.
describe('app controllers', function () {
beforeEach(module('app'));
describe('GreetingCtrl', function () {
it('should says hello world', inject(function ($controller) {
var $scope = {};
// this is the line that caused me pain
$controller('GreetingCtrl', { $scope: $scope });
expect($scope.title).toBe("Hello World!");
}));
});
});
I am currently using Jasmine with Karma(Testacular) and Web Storm to write unit test. I am having trouble spying on a method that gets called immediately when the controller is initialized. Is it possible to spy on a method that is called when the controller is initialized?
My controller code, the method I am attempting to spy on is getServicesNodeList().
myApp.controller('TreeViewController', function ($scope, $rootScope ,$document, DataServices) {
$scope.treeCollection = DataServices.getServicesNodeList();
$rootScope.viewportHeight = ($document.height() - 100) + 'px';
});
And here is the test spec:
describe("DataServices Controllers - ", function () {
beforeEach(angular.mock.module('myApp'));
describe("DataServicesTreeview Controller - ", function () {
beforeEach(inject(function ($controller, $rootScope, $document, $httpBackend, DataServices) {
scope = $rootScope.$new(),
doc = $document,
rootScope = $rootScope;
dataServices = DataServices;
$httpBackend.when('GET', '/scripts/internal/servicedata/services.json').respond(...);
var controller = $controller('TreeViewController', {$scope: scope, $rootScope: rootScope, $document: doc, DataServices: dataServices });
$httpBackend.flush();
}));
afterEach(inject(function($httpBackend){
$httpBackend.verifyNoOutstandingExpectation();
$httpBackend.verifyNoOutstandingRequest();
}));
it('should ensure DataServices.getServicesNodeList() was called', inject(function ($httpBackend, DataServices) {
spyOn(DataServices, "getServicesNodeList").andCallThrough();
$httpBackend.flush();
expect(DataServices.getServicesNodeList).toHaveBeenCalled();
}));
});
});
The test is failing saying that the method has not been called. I know that I should mock the DataServices and pass that into the test controller. But it seems like I would still have the same problem when spying on that method whether it is a mock or not. Anyone have any ideas or could point me to resources on the correct way to handle this?
When writing unit tests, you should isolate each piece of code. In this case, you need to isolate your service and test it separately. Create a mock of the service and pass it to your controller.
var mockDataServices = {
getServicesNodeList: function () {
return <insert your sample data here > ;
}
};
beforeEach(inject(function ($controller, $rootScope, $document) {
scope = $rootScope.$new(),
doc = $document,
rootScope = $rootScope;
var controller = $controller('TreeViewController', {
$scope: scope,
$rootScope: rootScope,
$document: doc,
DataServices: mockDataServices
});
}));
If it is your service that is making the $http request, you can remove that portion from your unit controller test. Write another unit test that tests that the service is making the correct http calls when it is initialized.