I have following code somewhere in my javascript:
var app = angular.module("App");
app.factory('rest', function($http) {
});
index.js:
document.addEventListener("deviceready", function() {
// How I am able to call rest here?
var domElement = document.documentElement;
angular.bootstrap(domElement, ["App"]);
var $body = angular.element(document.body);
var $rootScope = $body.scope().$root;
$rootScope.$apply(function () {
$rootScope.$broadcast('initialized', 'initialized');
});
});
You can register a run function to your module. So when you will bootstrap your module run will be executed and in the run you will be able to access the factory. Something like this:
// Code goes here
var app = angular.module("App");
app.factory('rest', function($http) {});
app.run(function(rest) {
console.log('your rest factory', rest);
});
document.addEventListener("deviceready", function() {
// How I am able to call rest here?
var domElement = document.documentElement;
angular.bootstrap(domElement, ["App"]);
var $body = angular.element(document.body);
var $rootScope = $body.scope().$root;
$rootScope.$apply(function() {
$rootScope.$broadcast('initialized', 'initialized');
});
});
Related
In my controller we have define the following two methods -
function goToHome() {
$state.go('app.home', {newReleaseIds: vm.newReleaseIds});
}
function createAnotherFuelRelease() {
// GA -- start creating another fuel release
$analytics.eventTrack('Start creating another fuel release', {category: 'Iron', label: moment().format('MMMM Do YYYY, h:mm:ss a')});
$state.go('app.create-iron', {selectedLocation: vm.selectedLocation, fuelReleaseNumber: vm.fuelReleaseNumber + 1, newReleaseIds: vm.newReleaseIds, pricing: $state.params.pricing});
}
called this two method from controller -
vm.createAnotherFuelRelease = createAnotherFuelRelease;
vm.goToHome = goToHome;
Now I would like to test those method from spec.js files -
it('should check goToHome()', function() {
// spyOn($state, 'go');
// $scope.inviteMembers(1);
// expect($state.go).toHaveBeenCalledWith('invite', {deptId: 1});
var spy = sinon.spy();
scope.vm.goToHome = {goToHome : spy};
scope.$digest();
expect($state.go).toHaveBeenCalledWith('app.home', {newReleaseIds: 1});
// expect(spy.calledOnce).toEqual(false);
// $compiledElement.find('.nv-button.test-gotToHome').trigger('click');
// expect(spy.calledOnce).toEqual(true);
// spy.reset();
});
but it doesn't work. If anyone knows that stuff please let me know.
Here you can find an example. It works with $location service but the idea is the same.
Replace $location with $state and be sure to load its module.
'use strict';
angular.module('test', []);
angular.module('test').controller('ctrl', function ctrlFactory($location) {
var vm = this;
vm.greet = function() {
$location.path('foo');
};
});
beforeEach(module('test'));
it('', inject(function($injector) {
// Given
var $controller = $injector.get('$controller');
var $location = $injector.get('$location');
var scope = $injector.get('$rootScope').$new();
spyOn($location, 'path');
var ctrl = $controller('ctrl', { $scope: scope });
// When
scope.$apply();
ctrl.greet();
//Then
expect($location.path).toHaveBeenCalled();
}));
I am trying to test a controller. The controller uses a service which is using $http to get the data from a json file (This json file is just a mock up of response returned from server)
My problem is that when I am testing the controller, it creates the controller object and even calls the service. But it doesnt call the $http mocked response. I not sure where I am going wrong. I tried looking at few examples but all of them are using $q.
My service looks like this:
(function(){
angular.module('mymodule')
.factory('MyService', MyService);
MyService.$inject = ['$http'];
function MyService($http) {
var service = {
retrieveData : retrieveData
};
return service;
function retrieveData(containerLabel){
var myGrossData = [];
var isMatchFound = false;
var myindex = containerLabel.slice(-4);
return $http.get('app/myGrossData.json').then(function(response) {
console.log('inside http retrieveData: ');
myGrossData = response.data;
var myindexExists = false;
var mydataObject = [];
var defaultdata = [];
angular.forEach(myGrossData, function (myGrossData) {
if (myindex === myGrossData.myindex) {
mydataObject = myGrossData;
isMatchFound = true;
}
if(!isMatchFound && myGrossData.myindex === '2006')
{
mydataObject = myGrossData;
}
if(myGrossData.myindex === '2006'){
defaultdata = myGrossData;
}
});
if (isMatchFound && response.status === 200)
{
return mydataObject;
}
else if(!isMatchFound && (response.status === 200 || response.status === 201)){
return defaultdata;
}
else //all other responses for success block
{
return 'Incorrect Response status: '+response.status;
}
},
function(error){
return 'Error Response: '+error.status;
}
);
}
};
})();
The controller calling it is :
(function () {
'use strict';
angular
.module('mymodule', [])
.controller('MyCtrl', MyCtrl);
MyCtrl.$inject = ['$scope', 'MyService'];
function MyCtrl($scope, MyService) {
var vm = this;
vm.datafromsomewhere = datafromsomewhere;
vm.displayData = [];
vm.disableBarCode = false;
vm.childCount = 0;
vm.headertext="Master Container Builder";
init();
function init() {
console.log('MyCtrl has been initialized!');
console.log(vm.headertext);
}
function myfunctionCalledByUI(input) {
processData(input);
}
function processData(containerLabel){
MyService.retrieveMasterContainer(containerLabel).then(function(data){
vm.displayData = data;
});
vm.disableBarCode = true;
vm.childCount = (vm.displayData.childData === undefined) ? 0: vm.displayData.childData.length;
vm.headertext="Myindex "+vm.displayData.myindex;
if ( vm.displayData.masterDataId.match(/[a-z]/i)) {
// Validation passed
vm.displayData.masterDataId ="No Shipping Label Assigned";
}
else
console.log('else: '+vm.displayData.masterDataId);
console.log('length of childData: '+vm.childCount);
}
}
})();
and finally my spec looks like this:
var expect = chai.expect;
describe('Test Controller', function () {
var rootScope, compile; MyService = {};
var $scope, $controller;
beforeEach(module('ui.router'));
beforeEach(function() {
module('mymodule');
inject(function ($rootScope, _$compile_,_$controller_) {
rootScope = $rootScope;
compile = _$compile_;
$scope = $rootScope.$new();
MyService = jasmine.createSpyObj('MyService', [
'retrieveData'
]);
$controller = _$controller_('MyCtrl', {
$scope: $scope
});
});
});
it('controller should be initialized and data should also be initialized', function() {
expect($controller).to.not.be.undefined;
expect($controller).to.not.be.null;
expect($controller.disableBarCode).to.equal(false);
expect($controller.childCount).to.equal(0);
expect($controller.headertext).to.equal("Master Container Builder");
});
it(' should process data when containerLabel is called into myfunction', function() {
$controller.handKeyed('12001');
expect(MyService.retrieveData).to.have.been.called;
expect($controller.processData).to.have.been.called;
expect($controller.disableBarCode).to.equal(true);
expect($controller.childCount).to.equal(0);
expect($controller.headertext).to.equal("Master Container Builder");
});
});
I am using following techstack if it helps:
angular 1.5
Ionic
Karma-jasmine
The code works when I run it. My issue is that when i run the test it doesnt populate the data in my vm.displayData variable. how do I make it get some data into the service. I added in some log statements and it skips it completely.
After all the test run including unrelated tests to this one, then I see the log statements from MyService. I am not sure how to approach this.
I think what you are looking for is the $httpBackend service. It will mock the request indicating the result. So, when your service hit the url, it will return what you passed to the $httpBackend configuration.
A simple example would be:
it('should list newest by category', function(){
$httpBackend
.expectGET(url)
.respond(techPosts /*YOUR MOCKED DATA*/);
$stateParams.category = 'tech';
var controller = $controller('HomeCtrl', { PostsResource: PostsResource, $stateParams: $stateParams });
controller.listNewestPosts();
$httpBackend.flush();
expect(controller.posts).toEqual(techPosts.posts);
});
So Im trying to figure out how to write unit tests for my angular controller. I am using karma as my runner. I was able to write 1 successful test but every time I try to write another test it yells at me about unexpected calls and such.
Here is my controller im trying to test.
(function (angular) {
'use strict';
var ngModule = angular.module('myApp.dashboardCtrl', []);
ngModule.controller('dashboardCtrl', function ($scope, $http) {
//"Global Variables"
var vm = this;
vm.success = false;
vm.repos = [];
//"Global Functions"
vm.addRepository = addRepository;
vm.listRepos = listRepos;
//Anything that needs to be instantiated on page load goes in the init
function init() {
listRepos();
}
init();
// Add a repository
function addRepository(repoUrl) {
$http.post("/api/repo/" + encodeURIComponent(repoUrl)).then(function (){
vm.success = true;
vm.addedRepo = vm.repoUrl;
vm.repoUrl = '';
listRepos();
});
}
//Lists all repos
function listRepos() {
$http.get('/api/repo').then( function (response){
vm.repos = response.data;
});
}
});
}(window.angular));
So I have a test written for listRepos(). It goes as follows
describe('dashboardCtrl', function() {
var scope, httpBackend, createController;
// Set up the module
beforeEach(module('myApp'));
beforeEach(inject(function($rootScope, $httpBackend, $controller) {
httpBackend = $httpBackend;
scope = $rootScope.$new();
createController = function() {
return $controller('dashboardCtrl', {
'$scope': scope
});
};
}));
afterEach(function() {
httpBackend.verifyNoOutstandingExpectation();
httpBackend.verifyNoOutstandingRequest();
});
it('should call listRepos and return all repos from the database', function() {
var controller = createController();
var expectedResponse = [{id: 12345, url: "https://github.com/myuser/myrepo.git"}];
httpBackend.expect('GET', '/api/repo')
.respond(expectedResponse);
httpBackend.flush();
scope.$apply(function() {
scope.listRepos;
});
expect(controller.repos).toEqual(expectedResponse);
});
This works and the test passes. Now my problem is I want to write another test to test the other function that calls a new api endpoint.
This is the test im trying to write for addRepository.
it('should addRepository to the database', function() {
var controller = createController();
var givenURL = "https://github.com/myuser/myURLtoMyRepo.git";
httpBackend.expect('POST', '/api/repo/' + encodeURIComponent(givenURL)).respond('success');
httpBackend.flush();
scope.$apply(function() {
scope.addRepository(givenURL);
});
expect(controller.success).toBe(true);
expect(controller.listRepos).toHaveBeenCalled();
});
The error I get when I add this test to the spec is:
Error: Unexpected request: GET /api/repo
Expected POST /api/repo/https%3A%2F%2Fgithub.com%2Fmyuser%2FmyURLtoMyRepo.git
at $httpBackend
Error: [$rootScope:inprog] $digest already in progress
http://errors.angularjs.org/1.4.8/$rootScope/inprog?p0=%24digest
The example I am working with is this one here
Any suggestions or tips is greatly appreciated!
UPDATE:
So changed my function to return the promise from the $http.post,
I rewrote my 2nd test and also wrapped my first test in a describe block describing the function its trying to test.
With the following:
describe('addRepository', function () {
it('should addRepository to the database', function () {
var controller = createController();
var givenURL = "https://github.com/myuser/myURLtoMyRepo.git";
httpBackend.expect('POST', '/api/repo/' + encodeURIComponent(givenURL)).respond('success');
scope.$apply(function () {
scope.addRepository(givenURL);
});
httpBackend.flush();
expect(controller.success).toBe(true);
});
it('should call listRepos', function() {
var controller = createController();
httpBackend.expect('GET', '/api/repo').respond('success');
controller.controller().then(function (result) {
expect(controller.listRepos).toHaveBeenCalled();
});
httpBackend.flush();
});
});
I still get the error:
Error: Unexpected request: GET /api/repo
Expected POST /api/repo/https%3A%2F%2Fgithub.com%2Fmyuser%2FmyURLtoMyRepo.git
at $httpBackend
Error: [$rootScope:inprog] $digest already in progress
but also
TypeError: 'undefined' is not a function (evaluating 'controller.controller()')
Error: Unflushed requests: 1
which shows 2 tests failed.
The flush should come after the call to the function. I'd also change the function to return the promise from the $http.post:
// Add a repository
function addRepository(repoUrl) {
return $http.post("/api/repo/" + encodeURIComponent(repoUrl)).then(function (){
vm.success = true;
vm.addedRepo = vm.repoUrl;
vm.repoUrl = '';
listRepos();
});
}
And then in the test you can call it and test the success part:
EDIT
I changed the controller.controller() to what you have.
it('should call listRepos', function() {
// Your setup
ctrl.addRepository().then(function(result) {
expect(ctrl.listRepos).toHaveBeenCalled();
});
});
EDIT 2
I emulated as best i could your code and the tests I write for the code:
(function () {
'use strict';
angular
.module('myApp')
.controller('DashboardController',DashboardController);
DashboardController.$inject = ['$http'];
function DashboardController($http) {
var vm = this;
vm.success = false;
vm.repos = [];
vm.addRepository = addRepository;
vm.listRepos = listRepos;
init();
// Anything that needs to be instantiated on page load goes in the init
function init() {
vm.listRepos();
}
// Add a repository
function addRepository(repoUrl) {
return $http.post('http://jsonplaceholder.typicode.com/posts/1.json').then(function (){
vm.success = true;
vm.addedRepo = vm.repoUrl;
vm.repoUrl = '';
vm.listRepos();
});
}
// Lists all repos
function listRepos() {
return $http.get('http://jsonplaceholder.typicode.com/posts/1').then( function (response){
vm.repos = response.data;
});
}
};
}());
Here I'm using an online JSONPlaceholder API to simulate HTTP calls as I, obviously, can't hit what you're pointing at. And for the test (which all pass):
(function() {
'use strict';
fdescribe('DashBoardController', function() {
var $rootScope, scope, ctrl, $httpBackend;
beforeEach(module('myApp'));
beforeEach(inject(function(_$rootScope_, _$httpBackend_,$controller) {
$rootScope = _$rootScope_;
scope = $rootScope.$new();
$httpBackend =_$httpBackend_;
ctrl = $controller('DashBoardController',{
$scope: scope
});
}));
beforeEach(function() {
// Setup spies
spyOn(ctrl,'listRepos');
});
describe('controller', function() {
it('should be defined', function() {
expect(ctrl).toBeDefined();
});
it('should initialize variables', function() {
expect(ctrl.success).toBe(false);
expect(ctrl.repos.length).toBe(0);
});
});
describe('init', function() {
it('should call listRepos', function() {
$httpBackend.expectGET('http://jsonplaceholder.typicode.com/posts/1')
.respond({success: '202'});
$httpBackend.expectPOST('http://jsonplaceholder.typicode.com/posts/1.json')
.respond({success: '202'});
ctrl.addRepository().then(function(result) {
expect(ctrl.success).toBe(true);
expect(ctrl.repoUrl).toBe('');
expect(ctrl.listRepos).toHaveBeenCalled();
});
$httpBackend.flush();
});
});
});
}());
I have a demo application where I have a controller which has a factory as dependency and factory itself depends on another service. My code is as follows:
var app = angular.module('sampleApp', ['ui.router']);
app.service("someServ", function(){
this.sendMsg = function(name){
return "Hello " + name;
}
})
app.factory("appFactory", function ($http, someServ) {
function getData(url) {
return $http.get(url);
}
function foo(){
var text = someServ.sendMsg("Monotype");
alert(text);
}
return {
getData : getData,
foo : foo
}
})
var productsController = function ($scope, $http, appFactory) {
var pct = this;
pct.url = "http://mysafeinfo.com/api/data?list=englishmonarchs&format=json";
var jsonDataPromise = appFactory.getData(pct.url);
jsonDataPromise
.then(function (response) {
pct.jsonData = response.data;
}, function (err) {
console.log("Error is: " + error);
});
pct.profun = function(){
appFactory.foo();
}
};
app.controller("productsController", productsController);
productsController.$inject = ['$scope', '$http', 'appFactory'];
I have to test with karma using Jasmine 2.4 as testing framework. I have tried many online tutorials but getting totally confused as everyone tries to do something different. Some use $provide to mock the service, some simply inject the actual service/factory and use a reference, some do not give any example of passing arguments to services.
Can someone please tell me how to do unit testing in simple terms. I have already tried doing something like this:
describe('unit testing of service and controller', function(){
beforeEach(module('sampleApp'));
var prodCtrl, $prodScope, mockfactory, mockservice;
beforeEach(function(){
mockservice = {
sendMsg : function(name){
return name;
}
}
module(function($provide){
$provide.value("someServ", mockservice);
});
inject(function($rootScope, $controller, $http, $q, appFactory){
appFactory = appFactory;
spyOn(appFactory, 'getData');
spyOn(appFactory, 'foo');
$prodScope = $rootScope.$new();
prodCtrl = $controller('productsController', {
$scope: $prodScope, $http: $http, appFactory:appFactory
});
});
});
it('appFactory has method getData and foo', function(){
appFactory.getData();
appFactory.foo();
expect(appFactory.getData).toHaveBeenCalled();
expect(appFactory.foo).toHaveBeenCalled();
})
it('productsController gets a promise back from getData', function(){
var url = "sample url";
var myPromise = prodCtrl.getData(url);
myPromise.then(function(){console.log("Promise returned");})
})
it('foo calls service method sendMsg', function(){
prodCtrl.profun();
expect(mockservice.sendMsg).toHaveBeenCalled();
})
});
I was finally able to solve this issue. My code looks like this:
var app = angular.module('sampleApp', []);
app.service("someServ", function(){
this.sendMsg = function(name){
return "Hello " + name;
}
})
app.factory("appFactory", function ($q, someServ) {
function getData() {
var defer = $q.defer();
defer.resolve("Success message");
return defer.promise;
}
function foo(){
var text = someServ.sendMsg("Monotype");
alert(text);
}
return {
getData : getData,
foo : foo
}
})
app.controller("mainController", ['$scope', '$http','appFactory', function($scope, $http, appFactory){
var mct = this;
mct.printData = function(){
var myPromise = appFactory.getData();
myPromise
.then(function(data){
alert("Promise returned successfully. Data : " + data);
}, function(error){
alert("Something went wrong.... Error: " + error);
})
}
mct.showMsg = function(){
appFactory.foo();
}
}]);
The test case looked like this:
describe('unit testing', function(){
var jsonData = {
name: "Aosis",
id: 12345
}
beforeEach(module('sampleApp'));
beforeEach(module(function($provide){
$provide.service("someServ", function(){
//this.sendMsg = function(param){}
this.sendMsg = jasmine.createSpy('sendMsg').and.callFake(function(param){})
});
$provide.factory("appFactory", function(someServ, $q){
function getData(){
var defer = $q.defer();
defer.resolve("Success message");
return defer.promise;
}
function foo(){
var facParam = "some text";
someServ.sendMsg(facParam);
}
return {
getData : getData,
foo : foo
}
});
}));
var $scope, mainController, appFactoryMock, someServMock;
beforeEach(inject(function($rootScope, $controller, $http, $q, appFactory, someServ){
appFactoryMock = appFactory;
someServMock = someServ;
$scope = $rootScope.$new();
mainController = $controller("mainController", {
$scope : $scope,
$http : $http,
appFactory : appFactoryMock
});
}));
// Tests go here....
});
Here, I have mocked service method as jasmine spy and specified the function that should get executed usingand.callFake(function(){.....}). A fake factory has been created and its methods have been spied upon. I tried to create fake factory similar to service using jasmine.createSpy but return {
getData : getData,
foo : foo
} was giving error. Hence, I did that.
Anyone, else if has better solution or some other explanation, please share.
I'm trying to write an AngularJS library for Pusher (http://pusher.com) and have run into some problems with my understanding of the digest loop and how it works. I am writing what is essentially an Angular wrapper on top of the Pusher javascript library.
The problem I'm facing is that when a Pusher event is triggered and my app is subscribed to it, it receives the message but doesn't update the scope where the subscription was setup.
I have the following code at the moment:
angular.module('pusher-angular', [])
.provider('PusherService', function () {
var apiKey = '';
var initOptions = {};
this.setOptions = function (options) {
initOptions = options || initOptions;
return this;
};
this.setToken = function (token) {
apiKey = token || apiKey;
return this;
};
this.$get = ['$window',
function ($window) {
var pusher = new $window.Pusher(apiKey, initOptions);
return pusher;
}];
})
.factory('Pusher', ['$rootScope', '$q', 'PusherService', 'PusherEventsService',
function ($rootScope, $q, PusherService, PusherEventsService) {
var client = PusherService;
return {
subscribe: function (channelName) {
return client.subscribe(channelName);
}
}
}
]);
.controller('ItemListController', ['$scope', 'Pusher', function($scope, Pusher) {
$scope.items = [];
var channel = Pusher.subscribe('items')
channel.bind('new', function(item) {
console.log(item);
$scope.items.push(item);
})
}]);
and in another file that sets the app up:
angular.module('myApp', [
'pusher-angular'
]).
config(['PusherServiceProvider',
function(PusherServiceProvider) {
PusherServiceProvider
.setToken('API KEY')
.setOptions({});
}
]);
I've removed some of the code to make it more concise.
In the ItemListController the $scope.items variable doesn't update when a message is received from Pusher.
My question is how can I make it such that when a message is received from Pusher that it then triggers a digest such that the scope updates and the changes are reflected in the DOM?
Edit: I know that I can just wrap the subscribe callback in a $scope.$apply(), but I don't want to have to do that for every callback. Is there a way that I can integrate it with the service?
On the controller level:
Angular doesn't know about the channel.bind event, so you have to kick off the cycle yourself.
All you have to do is call $scope.$digest() after the $scope.items gets updated.
.controller('ItemListController', ['$scope', 'Pusher', function($scope, Pusher) {
$scope.items = [];
var channel = Pusher.subscribe('items')
channel.bind('new', function(item) {
console.log(item);
$scope.items.push(item);
$scope.$digest(); // <-- this should be all you need
})
Pusher Decorator Alternative:
.provider('PusherService', function () {
var apiKey = '';
var initOptions = {};
this.setOptions = function (options) {
initOptions = options || initOptions;
return this;
};
this.setToken = function (token) {
apiKey = token || apiKey;
return this;
};
this.$get = ['$window','$rootScope',
function ($window, $rootScope) {
var pusher = new $window.Pusher(apiKey, initOptions),
oldTrigger = pusher.trigger; // <-- save off the old pusher.trigger
pusher.trigger = function decoratedTrigger() {
// here we redefine the pusher.trigger to:
// 1. run the old trigger and save off the result
var result = oldTrigger.apply(pusher, arguments);
// 2. kick off the $digest cycle
$rootScope.$digest();
// 3. return the result from the the original pusher.trigger
return result;
};
return pusher;
}];
I found that I can do something like this and it works:
bind: function (eventName, callback) {
client.bind(eventName, function () {
callback.call(this, arguments[0]);
$rootScope.$apply();
});
},
channelBind: function (channelName, eventName, callback) {
var channel = client.channel(channelName);
channel.bind(eventName, function() {
callback.call(this, arguments[0]);
$rootScope.$apply();
})
},
I'm not really happy with this though, and it feels as though there must be something bigger than I'm missing that would make this better.