Angular testing http request error - javascript

I am trying to test a http request with dynamic url
I have something in my service file like
My service file.
//other service codes..
//other service codes..
var id = $cookies.id;
return $http.get('/api/product/' + id + '/description')
//id is dynamic
Test file
describe('test', function () {
beforeEach(module('myApp'));
var $httpBackend, testCtrl, scope;
beforeEach(inject(function (_$controller_, _$httpBackend_, _$rootScope_) {
scope = _$rootScope_.$new();
$httpBackend = _$httpBackend_;
testCtrl = _$controller_('testCtrl', {
$scope: scope
});
}));
it('should check the request', function() {
$httpBackend.expectGET('/api/product/12345/description').respond({name:'test'});
$httpBackend.flush();
expect(scope.product).toBeDefined();
})
});
I am getting an error saying
Error: Unexpected request: GET /api/product/description
I am not sure how to test the dynamic url. Can anyone help me about it? Thanks a lot!

You don't have id set in your code, so the url becomes:
/api/product//description
Which is reduced to what you see in the unexpected request (// -> /)
So, why isn't id defined? Show the code where you set it.
In testing 'dynamic' urls, you need to set up your test so that you know what the value of id is, and expect that. There isn't a way to expect patterns of urls.
You can modify the value of cookies by changing the first line of your describe block:
beforeEach(module('myApp', function($provide) {
$provide.value('$cookies', {
id: 3
});
}));
Now you can expect that id will be three when the URL call happens.
This is pretty crude though. You could also just inject $cookies in the second beforeEach block and set it
beforeEach(inject(function($cookies) {
$cookies.put('id', 3);
}))

Related

Unit testing angular service with dependencies

I have the following Jasmine unit test:
describe('myService', function () {
var myService, $q;
// Instantiate the app
beforeEach(module('myApp'));
beforeEach(inject(function (_myService_, fileSystemService, $q) {
myService = _myService_;
spyOn(fileSystemService, 'listFiles').and.callFake(function () {
var deferred = $q.defer();
deferred.resolve('mockresult');
return deferred.promise;
});
}));
it('checks the number of outbound files', inject(function ($rootScope) {
var result;
myService.sendOutboundFiles2().then(function (res) {
result = res;
});
$rootScope.$digest();
expect(result).toBe('mockresult');
}));
});
Which tests this very simple service function:
sendOutboundFiles2() {
return fileSystemService.listFiles('Cached/Outbound').then(function(outfiles) {
return outfiles;
})
}
However when the test runs, it fails with a spurious Error: Unexpected request: GET blah\blah\blah.html No more request expected at $httpBackend error but i have no idea why as neither this test nor the service dependencies do anything with $httpBackend.
MORE INFO
If i comment out my existing controller tests, I get this error:
If i add my controller tests back in, I get this error:
So depending on which tests i add or remove, the HTML file in the GET error changes. But all the controller tests run fine. WTF?!?!?!!??!?!!?
The problem is caused by Ionic's keen prefetching of all templates into a cache. No idea why this doesn't occur when testing a controller though. The problem only appears when i was testing a service. Any way, I found this thread: Karma test breaks after using ui-router and the relevant fix is to add this snippets before injecting any dependencies:
beforeEach(module(function($provide) {
$provide.value('$ionicTemplateCache', function(){} );
}));
This stubs out the $ionicTemplateCache and prevents it from trying to preload all ui-router templates into the Ionic cache.

Angular testing without mocked $http

I have a REST client written to be part of an angularJS application that I want to write tests for; I tried with Jasmine (using passthrough on the $httpBackend) but could not get it to talk to the real endpoint at all (which is a requirement).
Does anybody know of a sensible library that allows for this? Or alternatively, a way of wrestling Jasmine into submission?
you need to inject first $httpbackend
describe('MyController', function() {
var $httpBackend, $rootScope, createController, authRequestHandler;
// Set up the module
beforeEach(module('MyApp'));
beforeEach(inject(function($injector) {
// Set up the mock http service responses
$httpBackend = $injector.get('$httpBackend');
// backend definition common for all tests
authRequestHandler = $httpBackend.when('GET', '/auth.py')
.respond({userId: 'userX'}, {'A-Token': 'xxx'});
// Get hold of a scope (i.e. the root scope)
$rootScope = $injector.get('$rootScope');
// The $controller service is used to create instances of controllers
var $controller = $injector.get('$controller');
createController = function() {
return $controller('MyController', {'$scope' : $rootScope });
};
$httpBackend.when('GET', "/api/rest/").respond(data_to_respond);
}));
next write test cases
it('getTypes - should return 3 car manufacturers', function () {
service.getTypes().then(function(response) {
//expect will be here
});
$httpBackend.flush();
});

Karma + PhantomJS TypeError: undefined is not an object (evaluating scope.jackpot)

I am still very new to unit testing, and to be honest, there isn't anything that I could even think of testing, but I cannot build my app unless I have at least 1 test case, so I attempted to make the most simple test case I could, on the smallest block of code in the controller, and it doesn't seem to be working.
I believe it's an error in my test case, and not in my controller's code itself, because when I view my app in the browser with grunt serve the console shows no errors.
This is the error it gives me:
PhantomJS 2.1.1 (Linux 0.0.0) Controller: MainCtrl should attach a list of jackpot to the scope FAILED
/home/elli0t/Documents/Yeoman Projects/monopoly/app/bower_components/angular/angular.js:3746:53
forEach#[native code]
forEach#/home/elli0t/Documents/Yeoman Projects/monopoly/app/bower_components/angular/angular.js:323:18
loadModules#/home/elli0t/Documents/Yeoman Projects/monopoly/app/bower_components/angular/angular.js:3711:12
createInjector#/home/elli0t/Documents/Yeoman Projects/monopoly/app/bower_components/angular/angular.js:3651:22
workFn#/home/elli0t/Documents/Yeoman Projects/monopoly/app/bower_components/angular-mocks/angular-mocks.js:2138:60
TypeError: undefined is not an object (evaluating 'scope.jackpot') in /home/elli0t/Documents/Yeoman Projects/monopoly/test/spec/controllers/main.js (line 20)
/home/elli0t/Documents/Yeoman Projects/monopoly/test/spec/controllers/main.js:20:17
PhantomJS 2.1.1 (Linux 0.0.0): Executed 1 of 1 (1 FAILED) ERROR (0.04 secs / 0.007 secs)
This is my test case:
it('should attach a list of jackpot to the scope', function () {
expect(scope.jackpot.length).toBe(2);
});
And this is the block of code I'm attempting to run the test on:
var countInJackpot = localStorageService.get('jackpot');
$scope.jackpot = countInJackpot || [
{
letter: '$',
prize: '$1,000,000 Cash',
numbers: ['$611A','$612B','$613C','$614D','$615E','$616F','$617G','$618F'],
count: [0,0,0,0,0,0,0,0]
},
{
letter: '?',
prize: '$500,000 Vacation Home',
numbers: ['?619A','?620B','?621C','?622D','?632E','?624F','?625G','?626H'],
count: [0,0,0,0,0,0,0,0]
}
];
For the time being, I really just want to write 1 simple test case, so it will let me build the app. I'm currently studying unit testing, but I still don't feel ready to write more complex test cases on my own. I will save that for later.
I have included the entire contents of the files in a gist for reference, if needed, and I can include the contents of the karma.conf.js if necessary.
My gist
Within your test case, scope should be $scope?
OR
You probably haven't setup your testing environment to load in your controller.
Here is an example of mine on testing a controller... Angular makes the setup a little iffy to learn, But once you understand the flow. It's pretty great :)
I'm going to try and add as many comments to explain each piece as I can... but let me know if your need clarification. You might be using jasmine, but keep in mind, this is mocha, im using the angular mock library loaded in via the karma.conf.
describe('myController', function() {
var $scope,
createController;
// Runs before each test. Re-extantiating the controller we want to test.
beforeEach(inject(function($injector) {
// Get hold of a scope (i.e. the root scope)
$scope = $injector.get('$rootScope');
// The $controller service is used to create instances of controllers
var $controller = $injector.get('$controller');
createController = function() {
// Creates the controller instance of our controller.
// We are injecting $scope so we will have access to it
// after the controllers code runs
return $controller('myCtrl', {
'$scope': $scope
});
};
}));
describe('#myFunction', function() {
it('jackpot should contain two objects', function() {
expect($scope.jackpot.length).to.equal(2);
});
});
});
Hope that helped. Here's some of the resources I used to learn :) Good Luck!
https://quickleft.com/blog/angularjs-unit-testing-for-real-though/
http://jaketrent.com/post/run-single-mocha-test/
I would expect you'd want to test both cases of the localStorageService having and not having data. To do so, create a spy for localStorageService (see Spies) and write your tests like this...
'use strict';
describe('Controller: MainCtrl', function () {
var scope, localStorageService, localData;
beforeEach(function() {
localData = {};
module('monopolyApp');
localStorageService = jasmine.createSpyObj('localStorageService', ['get', 'set']);
localStorageService.get.and.callFake(function(key) {
return localData[key];
});
inject(function($rootScope) {
scope = $rootScope.$new();
});
});
it('assigns jackpots from local storage if present', inject(function($controller) {
localData.jackpot = 'whatever, does not matter';
$controller('MainCtrl', {
$scope: scope,
localStorageService: localStorageService
});
expect(localStorageService.get).toHaveBeenCalledWith('jackpot');
expect(scope.jackpot).toBe(localData.jackpot);
}));
it('assigns jackpots from default array if none present in local storage', inject(function($controller) {
$controller('MainCtrl', {
$scope: scope,
localStorageService: localStorageService
});
expect(localStorageService.get).toHaveBeenCalledWith('jackpot');
expect(scope.jackpot.length).toEqual(2);
// maybe include some other checks like
expect(scope.jackpot[0].letter).toEqual('$');
expect(scope.jackpot[1].letter).toEqual('?');
}));
});

Accessing $http data in Protractor / E2E tests (AngularJS)

I have a bunch of Unit tests that are going well, and I've started to add Protractor E2E tests to my project. I'm doing okay testing interactive elements on the page, but I'm having trouble testing for certain data being sent out of the browser.
For instance, I want to see if clicking a certain button produces a POST to a certain endpoint.
I have protractor set up using the following:
/*globals global*/
module.exports = function() {
'use strict';
var chai = require('chai')
, promised = require('chai-as-promised');
global.expect = chai.expect;
chai.use(promised);
}();
I understand how to use Protractor to interact:
it('send data to the "log" endpoint when clicked', function() {
var repeater = element.all(by.repeater('finding in data.items'));
repeater.get(0).click().then(function() {
// $http expectation
});
});
However, I don't know how to set up $httpBackend in Protractor so I can capture the data that gets sent as a result of the .click() event. Do I need an additional module?
In Karma/Mocha I would simply:
beforeEach(module('exampleApp'));
describe('logging service', function() {
var $httpPostSpy, LoggingService;
beforeEach(inject(function(_logging_, $http, $httpBackend) {
$httpPostSpy = sinon.spy($http, 'post');
LoggingService = _logging_;
backend = $httpBackend;
backend.when('POST', '/api/log').respond(200);
}));
it('should send data to $http.post', function() [
LoggingService.sendLog({ message: 'logged!'});
backend.flush();
expect($httpPostSpy.args[0][1]).to.have.property('message');
});
});
But I don't know how to get a reference to $httpBackend and inject modules in Protractor.
End to end testing is about testing the code is manner that is similar to how an end user will do. So verifying whether a remote request is made should be validated against a visible outcome, such as data getting loaded into a div or grid.
Still if you want to validate remote requests are made, you can create a mock back end setup using the ngMockE2E module, which contains a mock $htpBackend similar to the one in ngMock.
Look at the documentation on the $httpBackend https://docs.angularjs.org/api/ngMockE2E/service/$httpBackend
$httpBackend is for mocking a fake call to the server. In e2e test you normally do want to actually make a call to the server. It's important to note that most element locators in protractor return promises.
That means with this code your test will know to wait until the response from the server comes back and then assert that the text is the p tag is the correct data from the server.
my-file.spec.js
'use strict';
describe('The main view', function () {
var page;
beforeEach(function () {
browser.get('/index.html');
page = require('./main.po');
});
it('should have resultText defined', function () {
expect(page.resultText).toBeDefined()
})
it('should display some text', function() {
expect(page.resultText.getText()
.then())
.toEqual("data-that-should-be-returned");
});
});
my-file.po.js
'use strict';
var MainPage = function() {
this.resultText = element(by.css('.result-text'));
};
module.exports = new MainPage();

Get compiled template for controller in unit test

I have the following controller:
angular.module('app').controller('userList'
, ['$scope','appRules',function ($scope,appRules) {
$scope.isUserInRole=function(user,role){
console.log("exucuting userIsInRole:",user,role);//never happens
return appRules.userIsInRole(user,role);
}
window.sscope=$scope;
console.log($scope.menu);
}]);
This is used for the following route:
angular.module('app', [,'ui.bootstrap','ngRoute'])
.config(['$routeProvider','$locationProvider'
,function($routeProvider,$locationProvider){
$locationProvider.html5Mode(true);
$locationProvider.hashPrefix('!');
$routeProvider
.when('/admin/users',{
templateUrl:'app/js/partials/admin/users/list.html',
controller:'userList'
});
}]);
The template would have something that will check if user can see this page or not (even if someone hacks javascript it doesn't matter because when data is fetched or submitted the server checks as well).
<div data-ng-if="!isUserInRole(login.user,'Administrator')" class="red">
Please log in or log in as another user.
</div>
<div data-ng-if="isUserInRole(login.user,'Administrator')" class="green">
Page to edit users.
</div>
Now I would like to create a controller in my test and see when it renders if it renders correctly:
var controller,scope,element;
beforeEach(inject(function($controller
,$rootScope,$compile,appRules) {
scope=$rootScope;
scope.login={};
scope.isUserInRole=appRules.userIsInRole;
controller = $controller('userList',{
$scope:scope
});
//here I have the controller but how to get the html it generates
// when rendering the template?
element = $compile("<div ng-controller='userList'>"
+"<div data-ng-view=''></div></div>")(scope);
//here element isn't rendered at all, works on directives but how
// to do this with controllers?
console.log("element is:",element);
}));
I would like to write a test like
it('Check page won\'t show if user is not set or is not Administrator.'
, function() {
expect($(element).text().trim()
.indexOf("Please log in or log in as another user."))
.toBe(0,'Show login message when login.user is not set.');
});
Not sure how to get element though.
[update]
I'm actually trying to test a template, because this is used by a controller I was thinking testing it with a controller but as Jon suggested it can just be loaded as template.
How to make it compile and manipulate scope and compile again I don't know. The following:
var el = angular.element(
$templateCache
.get('app/js/partials/admin/users/list.html'));
$rootScope = _$rootScope_;
$rootScope.menu={login:{}};
el.scope($rootScope);
$rootScope.$apply();
console.log(el.text());
This gives me the output of both Please log in or log in as another user. and Page to edit users. Looks like element is just the raw element not compiled one.
Trying something like this doesn't do much either:
beforeEach(inject(function(_$compile_
, _$rootScope_,appSettings,$templateCache){
console.log("before each");
var el = angular.element($templateCache
.get('app/js/partials/admin/users/list.html'));
var compile=_$compile_;
var $rootScope=_$rootScope_;
$rootScope.login:{};
$rootScope.isUserInRole=appSettings.userIsInRole;
//I would think that compile would invoke the ng-if and appSettings.userIsInRole
// but no console log in that function is shown.
var element = compile($templateCache
.get('app/js/partials/admin/users/list.html'))($rootScope);
console.log("element is:",element.html());//=undefined
}));
Add your templates to the cache using ng-templates or ng-html2js so they are available when Karma runs. Then do this:
var scope = null;
beforeEach(inject(function($templateCache,_$compile_,_$rootScope_, _$controller_) {
//get the template from the cache
template = $templateCache.get('src/main/app/components/someController/widget-search.tpl.html');
$compile = _$compile_;
$rootScope = _$rootScope_;
$controller = _$controller_;
scope = $rootScope.new();
//compile it
element = $compile(template)(scope);
}));
//then shove your compiled element into your controller instance
it( 'should be instantiated since it does not do much yet', function(){
ctrl = $controller('SomeController',
{
$scope: scope,
$element: element
});
scope.$digest();
// my controller should set a variable called hasInstance to true when it initializes. Here i'm testing that it does.
expect( ctrl.hasInstance).toBeTruthy();
});
For more info, see http://angular-tips.com/blog/2014/06/introduction-to-unit-test-directives/
If you are using Karma you could use the ng-html2js karma pre processor which basically runs through your templates and turns them into module which you can include in your test which populate the $templateCache so your template is available.

Categories

Resources