I have a module with a greet factory attached to it:
angular.module('someModule', [])
.factory('greet', function(name) {
return function() {
return 'Hi ' + name + '!';
}
});
This factory injects a name which is a value defined in some other module.
angular.module('someOtherModule', [])
.value('name', 'example');
When testing this module, I would like to be able to change the value of my injectable name multiple times (once for each test) so that my tests can look something like:
// In my test fileā¦
// Initialise the module I am testing `greet` upon, and mock the other module which has a `name` value
beforeEach(mocks.module('someModule', function ($provider) {
$provider.value('name', 'Bob');
}));
var greet
beforeEach(mocks.inject(function ($injector) {
greet = $injector.get('greet');
});
it('should say "Bob"', function () {
expect(greet()).toBe('Hi Bob!');
});
// Now I need to change the `name` value to be "Bar" instead
it('should say "Bar"', function () {
expect(greet()).toBe('Hi Bar!');
});
How is this possible?
The two modules are composed with my app module:
angular.module('app', ['someModule', 'someOtherModule'])
You can use $provide.value('name', 'Bob'); to inject the value.
var myApp = angular.module('myApp', []);
myApp.factory('greet', function (name) {
return function () {
return 'Hi ' + name + '!';
}
});
myApp.value('name', 'example');
describe('myApp', function () {
beforeEach(angular.mock.module('myApp'));
it('should say "Bob"', function () {
module(function ($provide) {
$provide.value('name', 'Bob');
});
angular.mock.inject(function ($injector) {
var greet = $injector.get('greet');
expect(greet()).toBe('Hi Bob!');
})
});
it('should say "Bar"', function () {
module(function ($provide) {
$provide.value('name', 'Bar');
});
angular.mock.inject(function ($injector) {
var greet = $injector.get('greet');
expect(greet()).toBe('Hi Bar!');
})
});
});
I created a demo for you and hope it can shed some light!
Updated: demo
Demo
I was able to get this working, but I had to make a slight change to how the name dependency was injected (pointed out in the comment below). Given the app code:
angular.module("app", ["some", "other"]);
angular.module("some", []).factory('greet', function(name) { // name injected here
return function() { // the name dependency moved from here up one function signature
return 'Hi ' + name + '!';
};
});
angular.module("other", []).value('name', 'example');
This is as DRY as I could make the test:
describe("greet", function() {
var provide = injector = greet = undefined;
beforeEach(function() {
module('app', function($provide) {
provide = $provide;
});
inject(function($injector) {
injector = $injector;
});
greet = function() {
return injector.get('greet')();
};
});
describe("#greet", function() {
it("says Hi Bob", function() {
provide.value('name', 'Bob');
expect(greet()).toBe('Hi Bob!');
});
it("says Hi Biff", function() {
provide.value('name', 'Biff');
expect(greet()).toBe('Hi Biff!');
});
});
});
Related
Given the following test.
The $provided service is not being injected. If I debug the test in karma I can see that the service being provided is the real one, and not the mock.
The really weird thing, is that if I remove the $provide.service... I get an error Error: [$injector:unpr] Unknown provider: ficaServiceProvider <- ficaService. This clearly means that the service is getting registered, just not replaced?
describe("component: FicaStatusComponent",
function () {
var fs;
beforeEach(function () {
module("aureus",
function ($provide) {
$provide.service("ficaService", function () {
this.status = function () {
return $q(function (resolve, reject) {
resolve([{ documentType: { id: 1 } }]);
});
}
})
});
});
beforeEach(inject(function (_$componentController_, _ficaService_) {
$componentController = _$componentController_;
fs = _ficaService_;
}));
it("should expose a `fica` object", function () {
console.log('should expose');
var bindings = {};
var ctrl = $componentController("ficaStatus", null, bindings);
expect(ctrl.fica).toBeDefined();
});
it("compliant with no documents should not be compliant",
function () {
var ctrl = $componentController("ficaStatus");
expect(ctrl.fica.length).toEqual(1);
});
}
);
The second test compliant with no documents... is failing.
Chrome 56.0.2924 (Windows 10 0.0.0) component: FicaStatusComponent compliant with no documents should not be compliant FAILED
Error: Unexpected request: GET api/fica/status/
I have also tried this, expecting to have an empty object injected, but the "real" service is there nevertheless?
module("aureus", function($provide) {
$provide.value("ficaService", function() { return {}; });
$provide.service("ficaService", function() { return {}; });
});
Here is the implementation of the controller for the component:
var FicaStatusController = (function () {
function FicaStatusController($log, $loc, ficaService) {
var _this = this;
this.$log = $log;
this.$loc = $loc;
this.ficaService = ficaService;
this.fica = [];
this.ficaService.status(1234).then(function (_) { return _this.fica = _; });
}
FicaStatusController.$inject = ["$log", "$location", "IFicaStatusService"];
module("aureus").component("ficaStatus", new FicaStatusComponent());
module("aureus").service("IFicaStatusService", FicaStatusService);
The service is as follows:
var FicaStatusService = (function () {
function FicaStatusService($log, $http) {
this.$log = $log;
this.$http = $http;
}
FicaStatusService.prototype.status = function (accountNumber) {
var url = "api/fica/status/" + accountNumber;
this.$log.log("status: " + url);
return this.$http
.get(url)
.then(function (_) { return _.data; });
};
return FicaStatusService;
}());
...
You have added your service in your module like this:
module("aureus").service("IFicaStatusService", FicaStatusService);
That means that you will need to provide IFicaStatusService instead of ficaService with $provide.
My ctrl is like this:
(function() {
'use strict';
angular
.module('App')
.controller('DeviceStatesCtrl', DeviceStatesCtrl);
function DeviceStatesCtrl( $rootScope, $scope, $translate,DeviceStatesService) {
var vm = this;
DeviceStatesService.getObject().then(function(response){
vm.init(response);
});
vm.init= function(response){
$translate(['table.title']).then(function(translate){
some stuff here
}));
}
}})();
My jasmine test is like this:
describe('app module', function() {
//var controller = null;
var $controller, $translate,$compile,createController,DeviceStatesService,$translate, scope;
var mockInit= sth;
beforeEach(function () {
module('App');
});
// Provide will help us create fake implementations for our dependencies, do not useful
module(function($provide) {
// Fake StoreService Implementation returning a promise
//nothing works :(
$provide.value('DeviceStatesService', {
getStatesObject: function() {
return {
then: function(callback) {
return callback([{ some: "thing", hoursInfo: {isOpen: true}}]);
}
};
}
});
});
return null;
});
beforeEach(inject(function($controller,$rootScope, _$translate_, _DeviceStatesService_) {
scope = $rootScope.$new();
//for 'controller as' syntax
$controller('DeviceStatesCtrl as deviceStat', {
$scope: scope
});
createController = function(params) {
return $controller("DeviceStatesCtrl as deviceStat", {
$scope: scope,
$stateParams: params || {}
});
};
}));
describe("Unit:Device States controller", function() {
//test init function
it("init function get called correctly", function() {
//spyOn(DeviceStatesService, 'getStatesObject').and.callThrough();
//createController();
//expect(DeviceStatesService.getStatesObject).toHaveBeenCalled();
expect(scope.deviceStat.init).toBeDefined();
//in init, all things are warpped in the $translate
//spyOn(scope.deviceStat, 'init');
scope.deviceStat.init(mockInit);
scope.deviceStat.setChart('All');
//expect(scope.deviceStat.totalNum).toEqual(22);
});
});
});
My question is how to test the init function and the stuff in it? The init function is in a promise, which I do not know how to call it. As my code scope.deviceStat.init(mockInit), it do not work. Another question is in the $translate promise, how to pass parameter in it?
You use the done function, which is a parameter passed to the spec via the jasmine function it.. Example..
describe("My Test set", function() {
it("My Test", function(done) {
doAsync().then(function(result) {
done();
}).catch(function(err) {
fail();
}
}
}
Hope this helps.
I'm trying to figure out how to mock an angular provider for a unit test. In the following snippet I have a 'translate' provider that is used to determine which language will be displayed in a view by default. I'd like to inject a different version of this provider into my tests to ensure my app displays the right thing based on a provider's settings. What I'm doing right now clearly doesn't seem to be working. Thanks in advance for your help.
By the way, if you're wondering why a provider was used instead of something else like a service or a simple value, this is a contrived example that distills a problem I'm having in a larger application. I need to inject something into an application's config method, which means I need to mock a provider.
var app = angular.module('app', []);
app.config(function($provide) {
$provide.provider('translate', function() {
return {
$get: function() {
return {
language: 'en'
};
}
};
});
});
app.controller('ctl', function($scope, translate) {
if (translate.language === 'en') {
$scope.greeting = "Welcome to the application.";
} else {
$scope.greeting = "Velkommen til appen.";
}
});
// ---SPECS-------------------------
describe('App', function() {
beforeEach(angular.mock.module('app'));
describe('by default', function() {
beforeEach(angular.mock.inject(
function(_$compile_, _$rootScope_) {
const viewHtml = $('#view');
$compile = _$compile_;
$rootScope = _$rootScope_;
$rootScope.isOn = false;
elm = $(viewHtml);
$compile(elm)($rootScope);
$rootScope.$digest();
}));
it('shows English', function() {
expect(elm.text()).toMatch(/Welcome/);
});
});
describe('without English specified', function() {
beforeEach(angular.mock.module('app', function ($provide) {
$provide.provider('translate', function () {
return {
$get: function () {
return { preferredLanguage: 'no' };
}
};
});
}));
beforeEach(angular.mock.inject(
function(_$compile_, _$rootScope_) {
const viewHtml = $('#view');
$compile = _$compile_;
$rootScope = _$rootScope_;
$rootScope.isOn = false;
elm = $(viewHtml);
$compile(elm)($rootScope);
$rootScope.$digest();
}));
it('shows Norwegian', function() {
expect(elm.text()).toMatch(/Velkommen/);
});
});
});
// --- Runner -------------------------
(function() {
var jasmineEnv = jasmine.getEnv();
jasmineEnv.updateInterval = 1000;
var htmlReporter = new jasmine.HtmlReporter();
jasmineEnv.addReporter(htmlReporter);
jasmineEnv.specFilter = function(spec) {
return htmlReporter.specFilter(spec);
};
var currentWindowOnload = window.onload;
window.onload = function() {
if (currentWindowOnload) {
currentWindowOnload();
}
execJasmine();
};
function execJasmine() {
jasmineEnv.execute();
}
})();
<link href="http://jasmine.github.io/1.3/lib/jasmine.css" rel="stylesheet"/>
<script src="//cdnjs.cloudflare.com/ajax/libs/jquery/2.1.3/jquery.min.js"></script>
<script src="http://code.angularjs.org/1.4.9/angular.js"></script>
<script src="http://code.angularjs.org/1.4.9/angular-mocks.js"></script>
<script src="http://jasmine.github.io/1.3/lib/jasmine.js"></script>
<script src="http://jasmine.github.io/1.3/lib/jasmine-html.js"></script>
<div ng-app="app">
<div id="view" ng-controller="ctl">{{greeting}}</div>
</div>
you can do it like this: -
beforeEach(module('app', function ($provide) {
$provide.provider('translate', function() {
return {
$get: function() {
return {
language: 'fr'
};
}
};
});
}));
you can also put the above code in a util method, that will take the language code as a parameter, so you don't spread above code everywhere.
function configure($provide, $injector) {
$provide.provider("testservice", function () {
this.$get = function () {
this.property = 777;
};
});
var s = $injector.get("testservice");
The last line throws this error:
Unknown provider: testservice
Why so?
To access provide in config phase, we need to append 'Provider' to the name of the provider.
module.config(function ($provide, $injector) {
$provide.provider("testservice", function () {
this.$get = function () {
this.property = 777;
};
});
var s = $injector.get("testserviceProvider");
console.log(s)
});
I have a ViewModel defined as below:
(function(ko, myApp) {
myApp.HomeViewModel = function () {
this.message = ko.observable("Helloy.....");
this.toolBarIsVisible = ko.observable(true);
this.isDataDirectoryManager = ko.observable(true);
};
myApp.HomeViewModel.prototype = {
sayHi: function () {
this.message("World");
}
};
ko.applyBindings(new myApp.HomeViewModel());
}(window.ko, window.myApp || {}));
How do I write a qunit test that instantiates an instance of myApp.HomeViewModel.
Thanks
Martin
You need to include your app code in the test file. The test code could be something like this:
(function (ko, myApp) {
var vm;
module( "HomdeViewModel", {
setup: function() {
vm = new myApp.HomeViewModel();
},
teardown: function() { }
});
test('Can create HomdeViewModel', function () {
ok(vm instanceof myApp.HomeViewModel);
});
test('Sets default values', function () {
strictEqual(vm.message(), 'Helloy.....');
ok(vm.toolBarIsVisible());
ok(vm.isDataDirectoryManager());
})
test('Can change message', function () {
vm.sayHi();
strictEqual(vm.message(), 'World');
});
})(window.ko, window.myApp)
Here's a jsFiddle with an example: http://jsfiddle.net/danne567/ptW9k/