Angular resource testing: $httpBackend.flush() cause Unexpected request - javascript

I want to test angularjs resource.
'use strict';
/**
* AddressService provides functionality to use address resource in easy way.
*
* This is an example usage of method:
*
* `get`:
*
var a = AddressService.get({id: '1'},
function (data) {
// Work here with your resource
}
);
*
*/
App.factory('AddressService', function ($resource, $rootScope) {
var url = [
$rootScope.GLOBALS.API_PATH,
$rootScope.GLOBALS.API_VERSION,
'models/address/:id'
].join('/'),
actions = {
'get': {
method: 'GET',
params: {id: '#id'}
}
};
return $resource(url, {}, actions);
});
I created the test:
'use strict';
var $httpBackend;
describe('Service: AddressService', function () {
beforeEach(module('myApp'));
beforeEach(inject(function ($injector) {
var url_get = 'api/v1/models/address/5';
var response_get = {
address_line_1: '8 Austin House Netly Road',
address_line_2: 'Ilford IR2 7ND'
};
$httpBackend = $injector.get('$httpBackend');
$httpBackend.whenGET(url_get).respond(response_get);
}));
describe('AddressService get test', function () {
it('Tests get address', inject(function (AddressService) {
var address = AddressService.get({ id: '5'});
$httpBackend.flush();
expect(address.address_line_1).toEqual('8 Austin House Netly Road');
expect(address.address_line_2).toEqual('Ilford IR2 7ND');
}));
});
});
I am not experienced with angular very well.
I have set jasmine in karma.config.js.
AngularJS v1.0.6
Yoeman and Grunt manage project.
I try grunt test
Running "karma:unit" (karma) task
INFO [karma]: Karma server started at http://localhost:8080/
INFO [launcher]: Starting browser Chrome
INFO [Chrome 27.0 (Linux)]: Connected on socket id RFFUY5bW8Hb5eTu0n-8L
Chrome 27.0 (Linux) Service: AddressService AddressService get Tests get address FAILED
Error: Unexpected request: GET views/home.html
No more request expected
at Error (<anonymous>)
at $httpBackend (/home/bart/y/projects/x/frontend/app/components/angular-mocks/angular-mocks.js:910:9)
at sendReq (/home/bart/y/projects/x/frontend/app/components/angular/angular.js:9087:9)
at $http (/home/bart/y/projects/x/frontend/app/components/angular/angular.js:8878:17)
at Function.$http.(anonymous function) [as get] (/home/bart/y/projects/x/frontend/app/components/angular/angular.js:9021:18)
at $q.when.then.then.next.locals (/home/bart/y/projects/x/frontend/app/components/angular/angular.js:7394:34)
at wrappedCallback (/home/bart/y/projects/x/frontend/app/components/angular/angular.js:6797:59)
at wrappedCallback (/home/bart/y/projects/x/frontend/app/components/angular/angular.js:6797:59)
at /home/bart/y/projects/x/frontend/app/components/angular/angular.js:6834:26
at Object.Scope.$eval (/home/bart/y/projects/x/frontend/app/components/angular/angular.js:8011:28)
Error: Declaration Location
at window.jasmine.window.inject.angular.mock.inject (/home/bart/y/projects/x/frontend/app/components/angular-mocks/angular-mocks.js:1744:25)
at null.<anonymous> (/home/bart/y/projects/x/frontend/test/spec/services/userdata/AddressService.js:32:30)
at null.<anonymous> (/home/bart/y/projects/x/frontend/test/spec/services/userdata/AddressService.js:31:5)
at /home/bart/y/projects/x/frontend/test/spec/services/userdata/AddressService.js:5:1
..................................................................
Chrome 27.0 (Linux): Executed 67 of 67 (1 FAILED) (0.343 secs / 0.179 secs)
Warning: Task "karma:unit" failed. Use --force to continue.
If I remove $httpBackend.flush() in test. Test is passing. But I am getting undefined from address. I saw examples: example all use the flush() But only I get silly exception: Error: Unexpected request: GET views/home.html
views/home.html is my view in project directory. I have no idea how solve my problem I could not find any solution. Am I missing the point somehow?
Can anybody see mistake in my code? All suggestions will be appreciate.
EDIT
I have found that I need to use this:
$httpBackend.when('GET', /\.html$/).passThrough();
But another problem is I am getting:
TypeError: Object #<Object> has no method 'passThrough'

the error is caused by your test attempting to load the main page for your app. Since you have not told the test to expect this server call, an error is returned. See the documentation for $httpBackend for clarification on this point.
Your $templateCache workaround is designed for unit testing directives not anything else. You were quite close with:
$httpBackend.when('GET', /\.html$/).passThrough();
Since you don't need to do anything with the actual template file it's a safe and simple work around to add this to your beforeEach() block.
$httpBackend.whenGET(/\.html$/).respond('');
This stops you getting the TypeError you had.
TypeError: Object #<Object> has no method 'passThrough'
I had the same issue in my unit tests after upgrading to a new version of Angular JS and using respond('') worked fine.

I have finally found solution:
I tried to follow this: other post
So I added $templateCache:
'use strict';
var $httpBackend;
describe('Service: AddressService', function () {
beforeEach(module('myApp'));
beforeEach(inject(function ($injector, $templateChache) {
$templateCache.put('views/home.html', '.<template-goes-here />');
var url_get = 'api/v1/models/address/5';
var response_get = {
address_line_1: '8 Austin House Netly Road',
address_line_2: 'Ilford IR2 7ND'
};
$httpBackend = $injector.get('$httpBackend');
$httpBackend.whenGET(url_get).respond(response_get);
}));
describe('AddressService get test', function () {
it('Tests get address', inject(function (AddressService) {
var address = AddressService.get({ id: '5'});
$httpBackend.flush();
expect(address.address_line_1).toEqual('8 Austin House Netly Road');
expect(address.address_line_2).toEqual('Ilford IR2 7ND');
}));
});
});

Related

Storing global variable in a separate file for Protractor Tests

I am trying to create a separate inventory file for Protractor Test where I can store all the reusable variable to be used by different test scrips. The sample Variable list is called Vars.js and the specs should import the variables from this file and consume those. However, this fails as shown below. Can this approach actually be used for storing reusable variables? Can I actually create a separate inventory file for protractor tests outside of conf.js?
Vars.js has the following content :
"use strict";
exports.config = {
function() {
global.loginMain = 'https://mytestsite.com/auth/login';
global.TestText = 'I am the test Text';
}
};
and the spec file is as follows:
require ('./Vars.js')
require('..\\waitAbsent.js')
require("../node_modules/jasmine-expect/index.js")
describe('Vairables Import Test', function() {
console.log(global.loginMain);
console.log(global.TestText);
browser.get(global.loginMain);
it('Text Validation', function(){
expect(browser.getCurrentUrl()).toEqual('https://mytestsite.com/auth/login')
})
});
The log
[10:55:29] I/local - Selenium standalone server started at http://192.168.1.187:51256/wd/hub
undefined
undefined
Started
(node:17800) [DEP0005] DeprecationWarning: Buffer() is deprecated due to security and usability issues. Please use the Buffer.alloc(), Buffer.allocUnsafe(), or Buffer.from() methods
instead.
F
Failures:
1) Vairables Import Test encountered a declaration exception
Message:
TypeError [ERR_INVALID_ARG_TYPE]: The "url" argument must be of type string. Received type undefined
Stack:
TypeError [ERR_INVALID_ARG_TYPE]: The "url" argument must be of type string. Received type undefined
at Url.parse (url.js:152:11)
at urlParse (url.js:146:13)
at Url.resolve (url.js:661:29)
at Object.urlResolve [as resolve] (url.js:657:40)
at ProtractorBrowser.get (C:\FCPS_I\FCPS\node_modules\protractor\built\browser.js:653:17)
at Suite.<anonymous> (C:\FCPS_I\FCPS\TestBed_Scripts\TestBed.js:10:13)
at Object.<anonymous> (C:\FCPS_I\FCPS\TestBed_Scripts\TestBed.js:5:1)
1 spec, 1 failure
Update: a revised Vars.js where I used params as shown below also return the same failure.
"use strict";
exports.config = {
params: {
loginMain: 'https://dss-esy.insystechinc.com/auth/login',
TestText : 'I am the test Text',
}
};
The below approach should work for you.
conf.js
exports.config = {
framework: 'jasmine',
seleniumAddress: 'http://localhost:4444/wd/hub',
specs: ['app.js'],
onPrepare: async() => {
global.globalVariables = require('./globalVariables');
}
};
app.js
describe('desribe the test', () => {
it('the it', async () => {
console.log(globalVariables.loginMain);
console.log(globalVariables.TestText);
})
})
globalVariables.js
module.exports = {
loginMain :'https://mytestsite.com/auth/login',
TestText : 'I am the test Text'
}

Angular Unit Test Failing Expected spy

I have below controller to get the books list and single books detail. It's working as expected but the unit test is not working as expected.
books.controller.js
var myApp = angular.module('myApp');
function BooksController($log, $routeParams, BooksService) {
// we declare as usual, just using the `this` Object instead of `$scope`
const vm = this;
const routeParamId = $routeParams.id;
if (routeParamId) {
BooksService.getBook(routeParamId)
.then(function (data) {
$log.info('==> successfully fetched data for book id:', routeParamId);
vm.book = data;
})
.catch(function (err) {
vm.errorMessage = 'OOPS! Book detail not found';
$log.error('GET BOOK: SOMETHING GOES WRONG', err)
});
}
BooksService.getBooks()
.then(function (data) {
$log.info('==> successfully fetched data');
vm.books = data;
})
.catch(function (err) {
vm.errorMessage = 'OOPS! No books found!';
$log.error('GET BOOK: SOMETHING GOES WRONG', err)
});
}
BooksController.$inject = ['$log', '$routeParams', 'BooksService'];
myApp.controller('BooksController', BooksController);
Spec for above controller in which I want to test the getBook(id) service but somehow I am not able to pass the id of book.
describe('Get All Books List: getBooks() =>', () => {
const errMsg = 'OOPS! No books found!';
beforeEach(() => {
// injecting rootscope and controller
inject(function (_$rootScope_, _$controller_, _$q_, BooksService) {
$scope = _$rootScope_.$new();
$service = BooksService;
$q = _$q_;
deferred = _$q_.defer();
// Use a Jasmine Spy to return the deferred promise
spyOn($service, 'getBooks').and.returnValue(deferred.promise);
// The injector unwraps the underscores (_) from around the parameter names when matching
$vm = _$controller_('BooksController', {$scope: $scope, $service: BooksService});
});
});
it('should defined getBooks $http methods in booksService', () => {
expect(typeof $service.getBooks).toEqual('function');
});
it('should able to fetch data from getBooks service', () => {
// Setup the data we wish to return for the .then function in the controller
deferred.resolve([{ id: 1 }, { id: 2 }]);
// We have to call apply for this to work
$scope.$apply();
// Since we called apply, now we can perform our assertions
expect($vm.books).not.toBe(undefined);
expect($vm.errorMessage).toBe(undefined);
});
it('should print error message if data not fetched', () => {
// Setup the data we wish to return for the .then function in the controller
deferred.reject(errMsg);
// We have to call apply for this to work
$scope.$apply();
// Since we called apply, now we can perform our assertions
expect($vm.errorMessage).toBe(errMsg);
});
});
describe('Get Single Book Detail: getBook() =>', () => {
const errMsg = 'OOPS! Book detail not found';
const routeParamId = '59663140b6e5fe676330836c';
beforeEach(() => {
// injecting rootscope and controller
inject(function (_$rootScope_, _$controller_, _$q_, BooksService) {
$scope = _$rootScope_.$new();
$scope.id = routeParamId;
$service = BooksService;
$q = _$q_;
var deferredSuccess = $q.defer();
// Use a Jasmine Spy to return the deferred promise
spyOn($service, 'getBook').and.returnValue(deferredSuccess.promise);
// The injector unwraps the underscores (_) from around the parameter names when matching
$vm = _$controller_('BooksController', {$scope: $scope, $service: BooksService});
});
});
it('should defined getBook $http methods in booksService', () => {
expect(typeof $service.getBook).toEqual('function');
});
it('should print error message', () => {
// Setup the data we wish to return for the .then function in the controller
deferred.reject(errMsg);
// We have to call apply for this to work
$scope.$apply();
// expect($service.getBook(123)).toHaveBeenCalled();
// expect($service.getBook(123)).toHaveBeenCalledWith(routeParamId);
// Since we called apply, now we can perform our assertions
expect($vm.errorMessage).toBe(errMsg);
});
});
"Get Single Book Detail: getBook()" this suit is not working. Please help me, how to short out this kind of situation.
Error I am getting is below
Chrome 59.0.3071 (Mac OS X 10.12.5) Books Controller Get Single Book Detail: getBook() => should print error message FAILED
Expected 'OOPS! No books found!' to be 'OOPS! Book detail not found'.
Chrome 59.0.3071 (Mac OS X 10.12.5) Books Controller Get Single Book Detail: getBook() => should print error message FAILED
Expected 'OOPS! No books found!' to be 'OOPS! Book detail not found'.
at Object.it (test/client/controllers/books.controller.spec.js:108:38)
Chrome 59.0.3071 (Mac OS X 10.12.5): Executed 7 of 7 (1 FAILED) (0 secs / 0.068 secs)
.
Chrome 59.0.3071 (Mac OS X 10.12.5): Executed 7 of 7 (1 FAILED) (0.005 secs / 0.068 secs)
you need to mock $rootScope. with provide.
The value of id is not getting avaibale in controller which is undefined.
So, non-id condition getting executing.
$scope = _$rootScope_.$new();
$scope.id = routeParamId;
module(function ($provide) {
$provide.value('$rootScope', scope); //mock rootscope with id
});
Real router should never be used in unit tests, with ngRoute module preferably be excluded from tested modules.
$scope.id = routeParamId is assigned before controller instantiation, but it isn't used at all. Instead, it should be done with mocked $routeParams.
There's no $service service. It's called BooksService. Thus getBooks isn't a spy. It's preferable to mock the service completely, not only a single method.
mockedBooksService = jasmine.createSpyObj('BooksService', ['getBooks']);
var mockedData1 = {};
var mockedData2 = {};
mockedBooksService.getBooks.and.returnValues(
$q.resolve(mockedData1),
$q.resolve(mockedData2),
);
$vm = $controller('BooksController', {
$scope: $scope,
BooksService: mockedBooksService,
$routeParams: { id: '59663140b6e5fe676330836c' }
});
expect(mockedBooksService.getBooks).toHaveBeenCalledTimes(2);
expect(mockedBooksService.getBooks.calls.allArgs()).toEqual([
['59663140b6e5fe676330836c'], []
]);
$rootScope.$digest();
expect($vm.book).toBe(mockedData2);
// then another test for falsy $routeParams.id
The test reveals the problem in controller code. Since tested code is called on controller construction, $controller should be called every time in it. A good way to avoid this is to put initialization code into $onInit method that could be tested separately.
EDIT (removed original, 2am answer)
Are you using strict mode? There appear to be a few scoping issues going on:
On line 9 (in the "Get All Books List" spec), deferred is not declared, making it global implicitly
The last test ran on the "Get All Books List" spec fails the global deferred promise
On line 60 (in the "Get Single Book Detail" spec), deferredSuccess is declared with var making it local to the function passed to inject()
On line 70 (the test in question), where (I assume) you meant to reject the "Single Book" deferredSuccess, you're actually failing the global/list deferred promise. This has no effect, since as mentioned in item 2 that promise was already failed and Q ignores repeated rejections.
So, that should explain why the error is not what you think it should be.
deferred isn't the only variable with scoping issues in your example; those should be addressed. I suggest wrapping the file in an IFFE and using strict mode. It'll make the code more predictable and avoid issues like this.
Doing this will only get you halfway there; #estus's response should round out the job.

Using StackTrace.js with Angular.js

I'm implementing a logger for my whole MEAN.js project. I have a server side logger that works well, and I've set an endpoint to receive a POST request with an exception to log a client error. I'm using StackTrace.js for client side error logging, but I'm getting some errors; now I'm using StackTrace's error-stack-parser and still getting errors. I'm using a decorator on Angular's $exceptionHandler to achieve this:
$provide.decorator("$exceptionHandler",
function($delegate, traceService, $log, $window) {
return function(exception, cause) {
$delegate(exception, cause);
try {
var errorMessage = exception.toString();
var stackTrace = traceService.
parse(exception).
then(function(stackFrames) {
$.ajax({
type: 'POST',
url: '/logger',
contentType: 'application/json',
data: angular.toJson({
url: $window.location.href,
message: errorMessage,
type: 'exception',
stackFrace: stackFrames,
cause: cause || ''
})
}).fail(function() {
$log.warn('POST request failed');
});
}).catch(function(error) {
$log.warn('Error StackTrace');
});
} catch (loggingError) {
$log.warn('Error server-side logging failed');
$log.log(loggingError);
}
};
});
traceService is an Angular service that acts as a proxy for StackTrace/error-stack-parser functions. traceService.parse is equivalent to ErrorStackParser.parse. traceService is implemented as:
angular.module('core').factory('traceService', function() {
return {
parse: ErrorStackParser.parse
};
});
I've put this decorator code on the Angular app bootstrap, and I'm testing by throwing an error on a controller. When I run the app I get this error on the client console:
Error server-side logging failed
angular.js:13708 TypeError: this.parseV8OrIE is not a function
at Object.ErrorStackParser$$parse [as parse] (error-stack-parser.js:70)
at application.js:22
at invokeLinkFn (angular.js:9816)
at nodeLinkFn (angular.js:9215)
at compositeLinkFn (angular.js:8510)
at publicLinkFn (angular.js:8390)
at lazyCompilation (angular.js:8728)
at updateView (viewDirective.ts:278)
at Object.configUpdatedCallback [as configUpdated] (viewDirective.ts:226)
at configureUiView (view.ts:164)
It would seem like this is an issue with error-stack-parse; I can't seem to find any articles regarding this issue, so I'm sure it's something I'm doing wrong, the way I'm testing or something else. Can any provide some insight as to why this is failing?
Edit
I modified the code following Caramiriel's comment. It seems like I need to add all functions from error-stack-parser to my service. This is traceService:
angular.module('core').factory('traceService', function() {
return {
parse: ErrorStackParser.parse,
parseV8OrIE: ErrorStackParser.parseV8OrIE,
extractLocation: ErrorStackParser.extractLocation
};
});
Now I'm getting this error:
TypeError: StackFrame is not a constructor
at Object.<anonymous> (http://localhost:3000/lib/error-stack-parser/error-stack-parser.js:105:24)
at Array.map (native)
at _map (http://localhost:3000/lib/error-stack-parser/error-stack-parser.js:22:26)
at Object.ErrorStackParser$$parseV8OrIE [as parseV8OrIE] (http://localhost:3000/lib/error-stack-parser/error-stack-parser.js:95:20)
at Object.ErrorStackParser$$parse [as parse] (http://localhost:3000/lib/error-stack-parser/error-stack-parser.js:70:29)
at http://localhost:3000/application.js:22:11
at invokeLinkFn (http://localhost:3000/lib/angular/angular.js:9816:9)
at nodeLinkFn (http://localhost:3000/lib/angular/angular.js:9215:11)
at compositeLinkFn (http://localhost:3000/lib/angular/angular.js:8510:13)
at publicLinkFn (http://localhost:3000/lib/angular/angular.js:8390:30)
It looks like you are using error-stack-parser without its dependency on the stackframe project.
You'll get that dependency automatically if you use npm or bower.

Undefined : value.push(new Resource(item)

I'm trying to test a factory but I'm getting a weird error. I looked around but haven't been able to find a similar problem. Any idea on what I'm doing wrong?
TypeError: 'undefined' is not a function (evaluating 'value.push(new Resource(item))')
Using angluarjs v1.3.20
factory.js
'use strict';
//Business service used for communicating with the articles REST endpoints
angular.module('businesses').factory('Business', ['$resource',
function ($resource) {
return $resource('api/businesses/:businessId', {
businessId: '#_id'
}, {
update: {
method: 'PUT'
}
});
}
]);
test.js
describe('My Service', function () {
// Then we can start by loading the main application module
beforeEach(module(ApplicationConfiguration.applicationModuleName));
afterEach(inject(function($httpBackend){
//These two calls will make sure that at the end of the test, all expected http calls were made
$httpBackend.verifyNoOutstandingExpectation();
$httpBackend.verifyNoOutstandingRequest();
}));
it('mock http call', inject(function($httpBackend, Business) {
var resource = new Business({
_id:'abcd'
});
var arraya = [{
_id:'abcd'
}, {
_id:'abcde'
}];
//Create an expectation for the correct url, and respond with a mock object
$httpBackend.expectGET('api/businesses/abcd').respond(200, arraya)
resource.$query();
//Because we're mocking an async action, ngMock provides a method for us to explicitly flush the request
$httpBackend.flush();
//Now the resource should behave as expected
console.log(resource);
//expect(resource.name).toBe('test');
}));
});
xxxxxxxxxxxxxxxxxxxxxxxxxxx
Shouldn't you only expect after you flushed?
resource.$query();
//Because we're mocking an async action, ngMock provides a method for us to explicitly flush the request
$httpBackend.flush();
//Create an expectation for the correct url, and respond with a mock object
$httpBackend.expectGET('api/businesses/abcd').respond(200, arraya);

Idiomatic composition of $http and $httpBackend in AngularJS 1.2.2

I have an AngularJS/Jasmine unit test that works fine with AngularJS 1.0.7, but doesn't work in Angular 1.2.2:
servicesSpec.js
describe('services', function() {
beforeEach(module('workPadApp'));
describe('taskGateway', function() {
var sut;
beforeEach(inject(function(taskGateway) {
sut = taskGateway;
}));
describe('saving a task', function() {
it('GETs correctly', inject(function($httpBackend) {
$httpBackend.expectGET('/').respond({ foo : 'bar' });
sut.createTask({ taskRef : 'baz' });
$httpBackend.flush();
}))
})
})
})
app.js
var workPadApp = angular.module('workPadApp', ['workPadApp.services']);
services.js
angular.module('workPadApp.services', []).
factory('taskGateway', function($http, $q) {
return {
createTask : function(task) {
$http.get('/')
.success(function(response) {
console.log('success');
})
.error(function(response) {
console.log('error');
});
}
}
});
Expected result
When I run this with Karma using AngularJS 1.0.7, all is good:
LOG: 'success'
Chrome 33.0.1750 (Windows 7): Executed 1 of 1 SUCCESS (0.179 secs / 0.03 secs)
Actual result
However, when I run it with AngularJS 1.2.2, I get this output:
Chrome 33.0.1750 (Windows 7) services taskGateway saving a task GETs correctly FAILED
Error: No pending request to flush !
at Error (native)
at Function.$httpBackend.flush (c:/Users/Mark/Documents/Grean/HHM/Src/HHMFrontend/test/lib/angular/angular-mocks.js:1195:34)
at null.<anonymous> (c:/Users/Mark/Documents/Grean/HHM/Src/HHMFrontend/test/unit/servicesSpec.js:91:18)
at Object.invoke (c:/Users/Mark/Documents/Grean/HHM/Src/HHMFrontend/app/lib/angular.js:3641:28)
at workFn (c:/Users/Mark/Documents/Grean/HHM/Src/HHMFrontend/test/lib/angular/angular-mocks.js:1778:20)
Error: Declaration Location
at window.jasmine.window.inject.angular.mock.inject (c:/Users/Mark/Documents/Grean/HHM/Src/HHMFrontend/test/lib/angular/angular-mocks.js:1764:25)
at null.<anonymous> (c:/Users/Mark/Documents/Grean/HHM/Src/HHMFrontend/test/unit/servicesSpec.js:72:26)
at null.<anonymous> (c:/Users/Mark/Documents/Grean/HHM/Src/HHMFrontend/test/unit/servicesSpec.js:62:3)
at null.<anonymous> (c:/Users/Mark/Documents/Grean/HHM/Src/HHMFrontend/test/unit/servicesSpec.js:8:2)
Chrome 33.0.1750 (Windows 7): Executed 1 of 1 (1 FAILED) (0.349 secs / 0.133 secs)
I've looked at the documentation for $httpBackend, and the example there uses a much more imperative style of setup using an $injector service.
That surprises me, since the declarative style that worked in version 1.0.7 is much terser. Is this really the only correct way going forward, or am I missing something?
As it turns out the relevant angular-* modules must be the same version.
In this example, angular.js and angular-mocks.js had to be the same version, 1.2.2.

Categories

Resources