I have a angularjs application, which I generated with yeoman. In the karma.conf.js is a reference to test/mock/**/*.js. I have troubles to find out, how I use this folder. Currently I have a simple Service:
'use strict';
angular.module('tvcalApp')
.factory('Series', function ($resource) {
return $resource('/search/:search');
});
and a Test
'use strict';
var $httpBackend;
describe('Service: Series', function () {
// load the service's module
beforeEach(module('tvcalApp'));
// instantiate service
var Series;
beforeEach(inject(function (_Series_) {
Series = _Series_;
}));
beforeEach(inject(function ($injector) {
var url_get = '/search/The%20Simpsons';
var response_get = [{"seriesid": "71663"}];
$httpBackend = $injector.get('$httpBackend');
$httpBackend.whenGET(url_get).respond(response_get);
}));
it('should return a list if search for The Simpsons', function () {
var res = Series.query({search: 'The Simpsons'});
$httpBackend.flush();
expect(res[0].seriesid === 71663);
});
});
This is working. But I wonder If I could use the mock folder from the karma.conf.js for the mocking function. Is it possible to move the mock part into the mock folder and use it for all unit test?
I could not find any example or documentation for this folder. Can someone please point me to to an example or documentation how to use the mock folder.
Basically i have done something like this looking at angular-mocks.js:
Let's say may app is called ql. and i have a loginService that i want to mock:
mocks/servicesMock.js looks like this:
'use strict';
var ql = {};
ql.mock = {};
ql.mock.$loginServiceMockProvider = function() {
this.$get = function() {
var $service = {
login: function() { }
};
return $service;
};
};
angular.module('qlMock', ['ng']).provider({
$loginServiceMock: ql.mock.$loginServiceMockProvider
});
Then in my tests i can injeck $loginServiceMock:
'use strict';
describe('LoginController tests', function () {
// load the controller's module
beforeEach(module('ql'));
// load our mocks module
beforeEach(angular.mock.module('qlMock'));
var loginController,
loginServiceMock,
scope;
// Initialize the controller and a mock scope
// $loginSericeMock will be injected from serviceMocks.js file
beforeEach(inject(function ($controller, $rootScope, $loginServiceMock) {
scope = $rootScope.$new();
loginServiceMock = $loginServiceMock;
loginController = $controller('LoginController', {
$scope: scope,
loginService: loginServiceMock
});
}));
});
The example by #gerasalus is useful, but to answer the question:
mocks is just a folder to put your code in to keep your project organized and the code in tests short and to the point. By keeping all your mocks in one place, it is easier to reuse them in tests... copying them from one test to another would be bad practice from a DRY perspective.
So, for example, you might have a service called 'Foo'
app/service/foo.js
Then you might create a mock of that service, called 'FooMock'
test/mocks/service/foo.js
And then you would create tests and inject whatever mocks you need, as is shown in gerasulus's answer.
Related
I am new to angular js.. just built an sample controller.. and now want to unit test it.. not sure how to write mock it in jasmine..
TestApp.js
var TestApp = angular.module('TestApp');
TestApp.controller('TestCtrl', function($scope) {
$scope.test = "test";
});
})();
TestApp_Spec.js
var scope, ctrl;
//you need to inject dependencies first
beforeEach(inject(function($rootScope) {
$scope = $rootScope.$new();
}));
it('test value should be test', inject(function($controller) {
ctrl = $controller('TestCtrl', {
scope: $scope
});
expect(scope.test).toBe("test");
}));
I am using stand alone version of jasmine and included angular.min.js, angular.mocks.js,TestApp.js and TestApp_Spec.js in the sepc_runner.html
Test results are not showing up..
Need help in writing the correct test cases..
There are some fixes in your code like you've not inserted module in your test suit so jasmine is not able to find the controller.
You have not passed second parameter which is an array as dependency in your module i.e angular.module('TestApp',[]);
Here is the working plunker.
app.js
(function(){
var TestApp = angular.module('TestApp',[]);
TestApp.controller('TestCtrl',["$scope",function(scope) {
alert(scope)
scope.test = "test";
}]);
})();
Test Suit
var scope, ctrl;
describe('MyApp', function() {
beforeEach(module('TestApp'));
//you need to inject dependencies first
beforeEach(inject(function($controller,$rootScope) {
scope = $rootScope.$new();
$controller('TestCtrl', {
'$scope': scope
});
}));
it('test value should be test',function(){
expect(scope.test).toBe("test");
});
});
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.
I have an angular application with some global environment variables defined in an env.js file:
(function(sp) {
'use strict';
pk.env = pk.env || {};
// localhost
pk.env.baseUrl = 'http://localhost:8080/';
})(typeof exports === 'undefined' ? (this.pk = this.pk || {}) : exports);
These variables are used in multiple factories to make REST API calls:
'use strict';
angular.module('pkApp').factory('pkFactory', PKFactory);
function PKFactory($http) {
var urlBase = pk.env.baseUrl;
var apiUrl = 'v1/data';
var _pkFactory = {};
_pkFactory.getData = function() {
return $http.get(urlBase + apiUrl);
};
return _pkFactory;
}
I am writing unit tests for this factory using Jasmine and I keep getting the error:
ReferenceError: Can't find variable: pk
If I remove this variable reference from the factory, the tests run fine.
'use strict';
console.log('=== In pk.factory.spec');
describe('Unit: pkFactory', function() {
beforeEach(module("pkApp"));
var $httpBackend, $rootScope, pkFactory;
beforeEach(inject(function($injector) {
// Set up the mock http service responses
$httpBackend = $injector.get('$httpBackend');
$httpBackend.when('GET', 'v1/data').respond('Not found');
pkFactory = $injector.get('pkFactory');
}));
afterEach(function() {
$httpBackend.verifyNoOutstandingExpectation();
$httpBackend.verifyNoOutstandingRequest();
});
it('expects getData method to be defined', function(){
expect(pkFactory.getData()).toBeDefined();
$httpBackend.flush();
});
})
How do I inject value of 'pk.env.baseUrl' into the factory? I have tried using $window, but it didn't work.
As pretty much already answered here, you can also declare a global variable within your test file
var globalVar = "something";
describe('Your test suit', function() {
...
});
and if you are using Karma you can edit the karma.conf.js file to define it
// list of files / patterns to load in the browser
files: [
...,
'file-containing-the-global-variable.js'
],
You should avoid using the globals in Angular completely.
Convert the file to an angular value or constant:
angular.module('pkApp').value('pk', pk);
now you can change pkFactory to get the pk object injected
function PKFactory($http, pk) {
// pk is no longer from global scope, but injected from angular as an argument
var urlBase = pk.env.baseUrl;
var apiUrl = 'v1/data';
var _pkFactory = {};
_pkFactory.getData = function() {
return $http.get(urlBase + apiUrl);
};
return _pkFactory;
}
and in tests, you can now mock the pk to a different value (or not do anything and use the one from code)
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.
I trying to understand how do I work with Angularjs. It looks like nice framework, but I stuck with a little problem with DI...
How I can inject dependecies in "run" method of the module? I mean I able to do it, but it works only if I have service/factory/value with as same name as "run" parameter name.
I build a simple application do illustrate what I mean:
var CONFIGURATION = "Configuration"; //I would like to have App.Configuration
var LOG_SERVICE = "LogService"; //I would like to have App.Services.LogService
var LOGIN_CONTROLLER = "LoginController";
var App = {};
App.Services = {};
App.Controllers = {};
App = angular.extend(App, angular.module("App", [])
.run(function ($rootScope, $location, Configuration, LogService) {
//How to force LogService to be the logger in params?
//not var = logger = LogService :)
LogService.log("app run");
}));
//App.$inject = [CONFIGURATION, LOG_SERVICE]; /* NOT WORKS */
App.Services.LogService = function (config) {
this.log = function (message) {
config.hasConsole ? console.log(message) : alert(message);
};
};
App.Services.LogService.$inject = [CONFIGURATION];
App.service(LOG_SERVICE, App.Services.LogService);
App.Controllers.LoginController = function (config, logger) {
logger.log("Controller constructed");
}
//The line below, required only because of problem described
App.Controllers.LoginController.$inject = [CONFIGURATION, LOG_SERVICE];
App.factory(CONFIGURATION, function () { return { hasConsole: console && console.log }; });
Why I need it may you ask :) But in my mind, first off all to have meaningful namespaces to organize the code. It will also minimize name collision and in the last, when minifing the JS, the things breaks down, since it renamed to more shorten names.
I think that the reason
App.$inject = [CONFIGURATION, LOG_SERVICE];
doesn't work, is because you have 2 other parameters $rootScope & $location that you need to inject in the $inject. So it needs to be:
App.$inject = ["$rootScope", "$location", CONFIGURATION, LOG_SERVICE];
Another way you can inject your service is to use this version:
app.run(["$rootScope", "$location", CONFIGURATION, LOG_SERVICE,
function ($rootScope, $location, Configuration, LogService) {
}] );