I was following the instructions of the course 'Hands on agular' (https://code.tutsplus.com/courses/hands-on-angular), wrote the following controller test:
'use strict';
describe('Controller: EventsController', function () {
// load the controller's module
beforeEach(module('ekApp'));
var EventsController,
scope, http, response;
// Initialize the controller and a mock scope
beforeEach(inject(function ($controller, $rootScope, $httpBackend) {
http = $httpBackend;
response = [{ key: '1' }];
http.whenGET('/api/events').respond(response);
scope = $rootScope.$new();
EventsController = $controller('EventsController', {
$scope: scope
// place here mocked dependencies
});
}));
afterEach(function(){
http.verifyNoOutstandingExpectation();
http.verifyNoOutstandingRequest();
});
it('should request to api', function () {
http.expectGET('/api/events');
http.flush();
});
});
When i run this test, i get 'no pending request to flush' error...
Here is my EventsController:
'use strict';
angular.module('ekApp')
.controller('EventsController', function($scope, Event, Category){
$scope.categories = [{name: 'All'}];
$scope.serverCategories = Category.query(function(){
$scope.categories = $scope.categories.concat($scope.serverCategories);
});
console.log($scope.categories);
$scope.events = Event.query();
console.log($scope.events);
$scope.filterBy = {
search: '',
category: $scope.categories[0],
startDate: new Date(2015,4,1),
endDate: new Date(2016,1,14)
};
});
and my Event service, which returns resource:
'use strict';
angular
.module('ekApp')
.factory('Event', function($resource){
return $resource('/api/events/:id', { id: '#id' });
});
Just scroll up the terminal and you will see the actual error:
Unknown provider: $resourceProvider <- $resource <- Event
You need to use angular-resource as the dependency and add it as the dependent module to your app.
angular.module('ekApp',['ngResource'])
Also, don't forgot to add angular-resource.js in karma-conf.js
'app/bower_components/angular-resource/angular-resource.js',
Related
I am trying to write the test cass for the factory which is returing a JSON response.
But I am getting the error:
Error: [$injector:unpr] http://errors.angularjs.org/1.4.1/$injector/unpr?p0=serviceProvider%20%3C-%20service
at Error (native)
Here is my code:
(function () {
angular.module('uspDeviceService',[]).factory('getDevice', GetDevice);
GetDevice.$inject = ['$http'];
function GetDevice($http) {
getDeviceList = function() {
return $http.get("static/test-json/devices/device-list.json");
}
return {
getDeviceList: getDeviceList
}
}
}());
Code for Test case:
describe('Get Product test', function() {
beforeEach(module('uspDeviceService'));
var service, httpBackend, getDevice ;
beforeEach(function () {
angular.mock.inject(function ($injector) {
//Injecting $http dependencies
httpBackend = $injector.get('$httpBackend');
service = $injector.get('service');
getDevice = $injector.get('getDevice');
})
});
console.log('Injection Dependencies is done');
describe('get Device List', function () {
it("should return a list of devices", inject(function () {
httpBackend.expectGET("static/test-json/devices/device-list.json").respond("Response found!");
httpBackend.flush();
}))
})
});
I am new to Angular Unit testing, can anyone please help me, where I am going wrong..
Two things that jump out at me:
Your angular.module declaration is defining a module, not getting the module. I would encourage you to split that up so that it's a fair bit more clear what your intent is.
angular.module('uspDeviceService', []);
angular.module('uspDeviceService').factory('getDevice', GetDevice);
It likely works as-is, but clarity is important.
What is...service? It's not defined anywhere in your code, and Angular can't find it either, hence the error message. You may be looking to get getDevice instead. Also, name your test variable with respect to what it actually is, so you don't confuse yourself.
// defined above
var getDevice;
// while injecting
getDevice = $injector.get('getDevice');
Supposing that you have an angularjs controller myController defined in myModule. The controller do some action when the api call is success and shows a flash message when api returns success = false. The your controller code would be something like
angular.module('myModule')
.controller( 'myController', function ( $scope,flashService, Api ) {
Api.get_list().$promise.then(function(data){
if(data.success) {
$scope.data = data.response
}
else{
flashService.createFlash(data.message, "danger");
}
});
});
Now to test both success = true and success = false we
describe('myController', function(){
var $rootScope, $httpBackend, controller, flashService;
var apilink = 'http://apilink';
beforeEach(module('myModule'));
beforeEach(inject(function(_$httpBackend_,_$rootScope_, _$controller_, _flashService_) {
$rootScope = _$rootScope_;
$httpBackend = _$httpBackend_;
flashService = _flashService_;
controller = _$controller_("myController", {$scope: $rootScope});
}));
it('init $scope.data when success = true', function(){
$httpBackend.whenGET(apilink)
.respond(
{
success: true,
response: {}
});
$httpBackend.flush();
expect($rootScope.data).toBeDefined();
});
it('show flash when api request failure', function(){
spyOn(flashService, 'createFlash');
$httpBackend.whenGET(apilink)
.respond(
{
success: false
});
$httpBackend.flush();
expect(flashService.createFlash).toHaveBeenCalled();
});
});
You are always going to mock the response because here we are testing the javascript code behaviour and we are not concerned with the Api. You can see when success the data is initialized and when success is false createFlash is called.
As far as test for factory is concerned you can do
describe('Get Product test', function() {
beforeEach(module('uspDeviceService'));
var service, httpBackend, getDevice ;
beforeEach(function () {
inject(function ($injector) {
httpBackend = $injector.get('$httpBackend');
service = $injector.get('service');
getDevice = $injector.get('getDevice');
});
});
describe('get Device List', function () {
it("should return a list of devices", inject(function () {
httpBackend.expectGET("static/test-json/devices/device- list.json").respond("Response found!");
var result = getDevice.getDeviceList();
httpBackend.flush();
expect(result).toEqual('Response found!');
}));
});
});
I followed this post (http://gonehybrid.com/how-to-write-automated-tests-for-your-ionic-app-part-2/) to create a simple unit test using Karma & Jasmine for a Ionic controller, but i keep getting undefined errors while the stated objects have been defined. I'm i missing something obvious? By the way, i'm able to run referenced tests from the blog above successfully which makes me think i'm missing something in mine.
Errora are as follows:
TypeError: undefined is not an object (evaluating 'authMock.login') in /Users/projects/app/tests/unit-tests/login.controller.tests.js (line 65)
TypeError: undefined is not an object (evaluating 'deferredLogin.resolve') in /Users/projects/app/tests/unit-tests/login.controller.tests.js (line 71)
TypeError: undefined is not an object (evaluating 'deferredLogin.reject') in /Users/projects/app/tests/unit-tests/login.controller.tests.js (line 79)
Here's the controller:
angular.module('app').controller('LoginCtrl', function($scope, $state, $ionicPopup, $auth) {
$scope.loginData = {};
$scope.user = {
email: '',
password: ''
};
$scope.doLogin = function(data) {
$auth.login(data).then(function(authenticated) {
$state.go('app.tabs.customer', {}, {reload: true});
}, function(err) {
var alertPopup = $ionicPopup.alert({
title: 'Login failed!',
template: 'Please check your credentials!'
});
});
};
});
Here's the test:
describe('LoginCtrl', function() {
var controller,
deferredLogin,
$scope,
authMock,
stateMock,
ionicPopupMock;
// load the module for our app
beforeEach(angular.mock.module('app'));
// disable template caching
beforeEach(angular.mock.module(function($provide, $urlRouterProvider) {
$provide.value('$ionicTemplateCache', function(){} );
$urlRouterProvider.deferIntercept();
}));
// instantiate the controller and mocks for every test
beforeEach(angular.mock.inject(function($controller, $q, $rootScope) {
deferredLogin = $q.defer();
$scope = $rootScope.$new();
// mock dinnerService
authMock = {
login: jasmine.createSpy('login spy')
.and.returnValue(deferredLogin.promise)
};
// mock $state
stateMock = jasmine.createSpyObj('$state spy', ['go']);
// mock $ionicPopup
ionicPopupMock = jasmine.createSpyObj('$ionicPopup spy', ['alert']);
// instantiate LoginController
controller = $controller('LoginCtrl', {
'$scope': $scope,
'$state': stateMock,
'$ionicPopup': ionicPopupMock,
'$auth': authMock
});
}));
describe('#doLogin', function() {
// call doLogin on the controller for every test
beforeEach(inject(function(_$rootScope_) {
$rootScope = _$rootScope_;
var user = {
email: 'test#yahoo.com',
password: 'test'
};
$scope.doLogin(user);
}));
it('should call login on $auth Service', function() {
expect(authMock.login).toHaveBeenCalledWith(user);
});
describe('when the login is executed,', function() {
it('if successful, should change state to app.tabs.customer', function() {
deferredLogin.resolve();
$rootScope.$digest();
expect(stateMock.go).toHaveBeenCalledWith('app.tabs.customer');
});
it('if unsuccessful, should show a popup', function() {
deferredLogin.reject();
$rootScope.$digest();
expect(ionicPopupMock.alert).toHaveBeenCalled();
});
});
})
});
Here's my Karma config:
files: [
'../www/lib/ionic/js/ionic.bundle.js',
'../www/lib/angular-mocks/angular-mocks.js',
'../www/js/*.js',
'../www/js/**/*.js',
'unit-tests/**/*.js'
],
I think that your controller for tests is undefined. Try to replace first it function with this and check if is it defined.
it('controller to be defained', function() {
expect($controller).toBeDefined();
});
If it isn't, try to call controller with:
$controller = _$controller_;
I just switched my application from using the Jade template engine to use client side HTML in order to improve performance and decrease server requests. Everything is working fine in the application however I'm having an issue updating my unit tests.
I have the following test:
describe('Registration Controller Tests', function() {
var $controller, $scope, defer, registerSpy, doesUserExistSpy, auth, RegistrationCtrl;
beforeEach(module('enigmaApp'));
beforeEach(inject(function (_$controller_, _$rootScope_, $q) {
$controller = _$controller_;
$scope = _$rootScope_;
defer = $q.defer();
// Create spies
registerSpy = jasmine.createSpy('register').and.returnValue(defer.promise);
doesUserExistSpy = jasmine.createSpy('doesUserExist').and.returnValue(defer.promise);
auth = {
register: registerSpy,
doesUserExist: doesUserExistSpy
};
// Init register controller with mocked services and scope
RegistrationCtrl = $controller('RegistrationCtrl', {
$scope: $scope,
auth: auth
});
// digest to update controller with services and scope
$scope.$digest();
}));
describe('RegistrationCtrl.register()', function () {
beforeEach(function () {
$scope.user = {
email: 'bwayne#wayneenterprise.com',
first_name: 'Bruce',
last_name: 'Wyane',
password: 'password123'
}
});
it('should call auth.register() with $scope.user', function () {
$scope.register();
expect(auth.register).toHaveBeenCalledWith($scope.user);
});
});
Which results in the following error:
Error: Unexpected request: GET modules/home/home.html
No more requests expected
Any ideas what I need to do in order to mock the routes? I've tried a few things but nothings working so far.
Additional code:
RegistrationCtrl
.controller('RegistrationCtrl', function($scope, $state, auth) {
$scope.user = {};
$scope.userExists = false;
$scope.error = '';
$scope.register = function() {
auth.register($scope.user)
.then(function(response){
$state.go('secure.user');
})
.catch(function(err){
$scope.error = err;
});
};
});
assuming your static files are all in /modules:
$httpBackend.whenGET(/modules\/[\w\W]*/).passThrough();
I am using AngularJs controller to send data from form to Google Sheet. Using Jasmine I wrote unit-test, which raises the bellow issue:
Error: Unsatisfied requests: POST http://localhost:5000/google-form
at Function.$httpBackend.verifyNoOutstandingExpectation
(.../angular-mocks/angular-mocks.js:1474:13)
After googling and going through some questions in stackowerflow, I decided to post the question as I didn't find a solution for it.
Here is the code for your references:
Angular Controller
/* global $ */
'use strict';
angular.module('myApp')
.controller('QuickMessageCtrl', ['$scope', function ($scope) {
$scope.quickMessageButtonText = 'Send';
$scope.quickMessage = {
name: '',
email: '',
content: '',
};
function setSubmittingIndicators() {
$scope.quickMessageButtonText = '';
$scope.submitting = true;
}
$scope.postQuickMessageToGoogle = _.throttle(function() {
setSubmittingIndicators();
$.ajax({
url: 'https://docs.google.com/forms/d/MyFormKey/formResponse',
data: {
'entry.3' : $scope.quickMessage.name,
'entry.1' : $scope.quickMessage.email,
'entry.0' : $scope.quickMessage.content
},
type: 'POST',
dataType: 'jsonp',
statusCode: {
200: function (){
//show succes message;
}
}
});
}, 500);
}]);
Unit Test Code
'use strict';
describe('Controller: QuickMessageCtrl', function() {
var $httpBackend, $rootScope, $controller, scope, apiUrl;
beforeEach(module('myApp'));
beforeEach(inject(function($injector) {
$httpBackend = $injector.get('$httpBackend');
apiUrl = $injector.get('apiUrl');
$httpBackend.expect(
'POST',
apiUrl + 'google-form',
{'name': 'test', 'email': 'test#test.com', 'content': 'this is content'}
).respond(200);
$rootScope = $injector.get('$rootScope');
scope = $rootScope.$new();
$controller = $injector.get('$controller');
$controller('QuickMessageCtrl', { $scope: scope });
}));
afterEach(function() {
$httpBackend.verifyNoOutstandingExpectation();
$httpBackend.verifyNoOutstandingRequest();
});
describe('Successful form submit', function() {
beforeEach(function() {
scope.quickMessageForm = { $valid: true };
scope.quickMessage.email = 'test#test.com';
scope.quickMessage.name = 'test';
scope.quickMessage.content = 'this is test';
scope.postQuickMessageToGoogle();
});
it('should set submitting indicators on submit', function() {
expect(scope.quickMessageButtonText).toBe('');
});
});
});
Your tests says that the mock http backend should receive a POST at the URL
apiUrl + 'google-form'
which, given the error message, is http://localhost:5000/google-form.
But your controller never sends a POST to that URL. It sends a POST to https://docs.google.com/forms/d/MyFormKey/formResponse. And it doesn't do it using angular's $http service, but does it behind its back, using jQuery.
As #JB Nizet pointed you are using jQuery instead of angular methods. In fact you should refactor your code a bit.
Its a great practice to keep things separate, like Controller from Service. In your case you are using service inside the controller. I would rather suggest you to create a service and then import that service in your controller. So basically here how the code will look like:
Controller
/* global $ */
'use strict';
angular.module('myApp')
.controller('QuickMessageCtrl', ['$scope', 'MyNewService', function ($scope, MyNewService) {
$scope.quickMessageButtonText = 'Send';
$scope.quickMessage = {
name: '',
email: '',
content: '',
};
function resetFormData() {
$('#name').val('');
$('#email').val('');
$('#content').val('');
}
$scope.postQuickMessageToGoogle = _.throttle(function() {
setSubmittingIndicators();
MyNewService.sendQuickMessage(
$scope.quickMessage.name,
$scope.quickMessage.email,
$scope.quickMessage.content
)
.success(
//sucess Message
//can be as well a function that returns a status code
)
.error(
//error Message
);
}, 500);
}]);
Service
'use strict';
angular.module('myApp')
.factory('MyNewService', ['$http', function ($http) {
var myService = {};
myService.sendQuickMessage = function(name, email, content) {
$http({
method: 'JSONP',
url: 'https://docs.google.com/forms/d/MyFormKey/formResponse?'+
'entry.3=' + name +
'&entry.1=' + email +
'&entry.0=' + content
});
};
return myService;
}]);
Unit-test
'use strict';
describe('Controller: QuickMessageCtrl', function() {
var $httpBackend, $rootScope, $controller, scope, apiUrl;
beforeEach(module('myApp'));
beforeEach(inject(function($injector) {
$httpBackend = $injector.get('$httpBackend');
apiUrl = $injector.get('apiUrl');
$httpBackend.expectJSONP(
'https://docs.google.com/forms/d/MyFormKey/formResponse?'+
'entry.3=test'+
'&entry.1=test#test.com'+
'&entry.0=thisIsContent'
).respond(200, {});
$rootScope = $injector.get('$rootScope');
scope = $rootScope.$new();
$controller = $injector.get('$controller');
$controller('QuickMessageCtrl', { $scope: scope });
}));
describe('form submit', function() {
var changeStateSpy;
beforeEach(function() {
scope.quickMessage.name = 'test';
scope.quickMessage.content = 'thisIsContent';
scope.quickMessage.email ='test#test.com';
});
afterEach(function(){
$httpBackend.verifyNoOutstandingExpectation();
$httpBackend.verifyNoOutstandingRequest();
});
it('should set submitting indicators on submit', function() {
scope.postQuickMessageToGoogle();
expect(scope.quickMessageButtonText).toBe('');
$httpBackend.flush();
});
});
});
Given i have a service like this.
angular.module('app')
.factory('Session', function Session($rootScope, $cookieStore) {
var user;
if (user = $cookieStore.get('user')) {
$rootScope.currentUser = user;
}
});
and a test
'use strict';
describe('Service: Session', function () {
var Session,
_rootScope,
_cookieStore;
beforeEach(module('app'));
beforeEach(module(function($provide, $injector) {
_rootScope = $injector.get('$rootScope').$new();
_cookieStore = {
get: angular.noop
};
$provide.value('$rootScope', _rootScope);
$provide.value('$cookieStore', _cookieStore);
}));
beforeEach(inject(function(_Session_) {
Session = _Session_;
}));
it('transfers the cookie under user into the currentUser', function() {
spyOn(_cookieStore, 'get').andReturn('user');
inject(function(_Session_) {
Session = _Session_;
});
expect(_rootScope.currentUser).toEqual('user');
});
});
I end up getting
Error: [$injector:unpr] Unknown provider: $rootScope
http://errors.angularjs.org/1.2.6/$injector/unpr?p0=%24rootScope
Can someone explain to me what concept I'm missing? I'm finding unit testing services to be exceedingly difficult.
The trick was to use $injector to explicitly instantiate the service at a specific moment in time. (Thanks for your help #caitp)
'use strict';
describe('Service: Session', function () {
var _cookieStore;
beforeEach(module('rallyApp'));
beforeEach(module(function($provide) {
_cookieStore = {
get: angular.noop
};
$provide.value('$cookieStore', _cookieStore);
}));
it('transfers the cookie under user into the currentUser', function() {
inject(function($rootScope, $injector) {
spyOn(_cookieStore, 'get').andReturn('caitp');
$injector.get('Session');
expect($rootScope.currentUser).toBe('caitp');
});
});
});