Mock backend server (Google Forms) in AngurJS - angularjs

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();
});
});
});

Related

Unsure on how to successfully test this function using $httBackend

This is the function in the controller:
var vm = this;
vm.getData = getData;
function getData(val) {
return $http.get('/get-data', {
params: {
query: val
}
}).then(function(response) {
return response.data;
});
}
and this is my (stripped down) test file:
describe('Controller: MyCtrl', function() {
'use strict';
var MyCtrl;
var rootScope;
var scope;
var httpMock;
beforeEach(function() {
module('MyModule');
inject(function($controller, $rootScope, $httpBackend) {
rootScope = $rootScope;
scope = $rootScope.$new();
httpMock = $httpBackend;
MyCtrl = $controller('MyCtrl as vm', {
$rootScope: rootScope,
$scope: scope,
$http: httpMock,
});
});
});
describe('vm.getData()', function() {
it('returns the required data', function() {
httpMock.when('GET', '/get-data?query=test-val').respond(200, {data: 'test-data'});
httpMock.flush();
expect(scope.vm.getData('test-val')).toEqual('test-data');
});
});
});
I would like to test that the result if calling getData() return the correct data.
Currently I'm getting the error $http.get is not a function. Setting a breakpoint in my function shows that http is stubbed with $httpBackend though.
I think there is something fundamental I'm not grasping - any pointers would be greatly appreciated.
You shouldn't have to create the controller with:
$http: $httpBackend
to mock the backend. $httpBackend will mock the request itself already.
Further the test and assertion is done in the wrong order:
httpMock.when('GET', '/get-data?query=test-val').respond(200, {data: 'test-data'});
MyCtrl.getData('test-val').then(function(_result_){ //perform the request
result = _result_; //save the result of the promise
});
httpMock.flush(); //execute the request
expect(result).toBe('test-data'); //assert that the result is as expected

Struggling to get started on AngularJS unit testing

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_;

Jasmine: no pending request to flush

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',

Karma Unexpected Request: GET modules/home/home.html

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();

How to mock $scope.variables in jasmine

I have the following test case CompanyCtrlSpec.js
describe('ViewCompanyCtrl', function () {
var $rootScope, scope, $controller , $q ;
beforeEach(angular.mock.module('MyApp'));
beforeEach(inject(function ($rootScope, $controller ) {
scope = $rootScope.$new();
createController = function() {
return $controller('ViewCompanyCtrl', {
$scope: scope,
company : {}
});
};
}));
it('the company type should be equal to an object', function () {
var controller = new createController();
//some assertion
});
});
Following is ViewCompanyCtrl.js file
angular.module('MyApp').controller('ViewCompanyCtrl',
function ($scope, companyService, $state, meetingService, company, attachmentService) {
'use strict';
$scope.company = company;
$scope.companyInfo = {};
$scope.companyInfo['AName'] = [$scope.company.Address.Street, $scope.company.Address.ZipCode + ' ' + $scope.company.Address.City].join(', ');
//more code
});
Following is the app.routes.js file where company is getting resolved
.state('company', {
abstract: true,
url: '/company/:companyId',
resolve: {
company: function($q, $stateParams, companyService){
var deferred = $q.defer();
companyService
.getCompany($stateParams.companyId)
.error(function(data, status, headers){
//more code
})
.success(function(data){
deferred.resolve(data);
});
return deferred.promise;
}
},
My problem is that i get the following error
TypeError: $scope.company.Address is undefined in C:/Users/MyApp/WebApiRole/app/compan
y/ViewCompanyCtrl.js (line 8)
#C:/Users/MyApp/WebApiRole/app/company/ViewCompanyCtrl.js:8:42
I am guessing that this happens because i didn't mock the scope.company.Address in my test case . I am not sure how to do that . Appreciate it if Any one can help me with this , or any method to do this ?
It looks to me like the $scope.company is the same as the company that is injected into your controller. So you need only to set an Address on the company that you are injecting into your mock, like so:
beforeEach(inject(function ($rootScope, $controller ) {
scope = $rootScope.$new();
createController = function() {
return $controller('ViewCompanyCtrl', {
$scope: scope,
company : {
Address: {/* address data goes here */}
}
});
};
}));
If you want the company data to be different for each test, simply pass it into your createController() function:
beforeEach(inject(function ($rootScope, $controller ) {
scope = $rootScope.$new();
createController = function(company) {
return $controller('ViewCompanyCtrl', {
$scope: scope,
company : company
});
};
}));
it('the company type should be equal to an object', function () {
var company = {Address: {/* address data goes here */}};
var controller = new createController(company);
//some assertion
});
Try to add the controller to the definition of your route. A controller is not a singleton like the other service. It is usually tied to a state or a view.
.state('company', {
abstract: true,
url: '/company/:companyId',
controller: 'ViewCompanyCtrl'
resolve: {
company: function($q, $stateParams, companyService){
var deferred = $q.defer();
companyService
.getCompany($stateParams.companyId)
.error(function(data, status, headers){
//more code
})
.success(function(data){
deferred.resolve(data);
});
return deferred.promise;
}
},
Even better I would use the controller As convention instead of using scope in your controller. Then your controller will be come the scope.
Aside for that I would highly recommend having a look at John Papa's coding standard or Todd Motto's. Both are good and will recommend to use that convention.

Resources