AngularJS Service Test (Jasmine/Karma) - Error: Unexpected request: GET - angularjs

I'm currently trying to test an AngularJS service I've written using Karma and Jasmine. However, I'm currently running into an issue with $httpBackend and I cannot get around it. Here's my service and test:
Service:
export default angular.module("api:events", [])
.factory("Events", ($http) => {
let api = "http://localhost:5000/api/";
let Events = {};
Events.query = (params) => {
return $http.get(`${api}/events`, {params: params}).then((res) => {
return res.data;
});
};
Events.get = (params) => {
return $http.get(`${api}/events/` + params.id).then((res) => {
return res.data;
});
};
return Events;
});
Test:
describe("api:events", () => {
let EventsFactory, $scope, http;
beforeEach(module("app"));
beforeEach(inject((_Events_, $rootScope, $httpBackend) => {
EventsFactory = _Events_;
$scope = $rootScope.$new();
http = $httpBackend;
}));
it("should return an object", () => {
let data = {};
let url = "http://localhost:5000/api/events/:id";
let response = { id: "12345" };
http.whenGET(url).respond(200, response);
data = EventsFactory.get({ id: "1"});
expect(data).not.toEqual("12345");
http.flush();
expect(data).toEqual("12345");
http.verifyNoOutstandingExpectation();
http.verifyNoOutstandingRequest();
});
});
And the error I'm receiving (due to http.flush()):
Chrome 46.0.2490 (Mac OS X 10.10.5) api:events test FAILED
Error: Unexpected request: GET http://localhost:5000/api/events/1
No more request expected
If I log data after data = EventsFactory.get({ id: "1"}); I get Object{$$state: Object{status: 0}}.
I've also tried calling my service like this with similar results:
EventsFactory.get({ id: "1"}).then((result) => {
data = result;
});
Any thoughts?

The problem here is that URLs given to the whenXXX() and expectXXX() methods of the $http mocks have to be literal. One could intuitevely expect that URLs with parameters (e.g. the :id in the code from the question) will work, but that is not the case. So to correct the error, just replace:
let url = "http://localhost:5000/api/events/:id";
with:
let url = "http://localhost:5000/api/events/1"; // `1` is the literal id

Check out the documentation for verifyNoOutstandingExpectation() and verifyNoOutstandingRequest().
It says:
Verifies that all of the requests defined via the expect api were made.
The key words there are "expect api". You are not using the "expect" API, you're using then "when" API. You shouldn't be calling either of these methods at the end of your test when using the "when" API.
The documentation describes the differences between the "expect" and "when" API.

Related

Using $resource in AngularJS and getting an undefined response

I have the following code:
vm.getTheSector = function () {
SectorDataFactory.get({action:'getSector', sectorName:vm.selected}, function (response) {
vm.industry.sector = response.theSector;
});
vm.saveTheIndustry()
};
vm.saveTheIndustry = function () {
SectorDataFactory.save({action: 'saveIndustry'}, vm.industry, function (response) {
vm.responseMessage = response.theMessage;
});
};
I am getting a sector object from a RESTful web service however vm.industry.sector is undefined. I'm trying to then POST the industry object however the sector is null.
vm.getTheSector = function () {
SectorDataFactory.get({action: 'getSector', sectorName: vm.selected}, function (response) {
vm.industry.sector = response.theSector;
vm.saveTheIndustry()
});
};
Thanks to mbeso. The vm.saveTheIndustry() now executes as part of the success callback.

Error: Timeout - Async callback was not invoked within timeout specified by jasmine.DEFAULT_TIMEOUT_INTERVAL

I am trying to test my Ionic app with Jasmine. This is my test suit.
beforeEach(() => {
auth = new Authentication(<any>new HttpMock(), <any>new StorageMock())
user = new MockUser();
header = new Headers({'uid': '1'});
resp = new Response( new ResponseOptions({body: {name: user.name }, headers: header}))
});
it('facebok login ',(done)=>{
spyOn(auth.http,'post').and.returnValue(HttpMock.returnValue(resp));
spyOn(Facebook,'login').and.returnValue(Promise.resolve({authResponse: {accessToken: 1}}))
auth.facebookLogin().then((res)=>{
expect(auth.http.post.calls.argsFor(0)).toEqual([CONFIG.url.facebookUrl,{}])
expect(res.authResponse.accessToken).toEqual(1);
done();
},(err)=>{
done();
});
});
My HttpMock class to mock http calls looks like this.
export class HttpMock {
static returnValue(data){
return Observable.create((observer) => {
observer.next(data);
})
}
}
The relevant part in the service I am testing is,
facebookLogin(): Promise<any>{
let permissions = ["public_profile","email"];
return Facebook.login(permissions)
.then( (response) => {
let token = { access_token: response.authResponse.accessToken };
return this.login( token ,'facebookUrl').toPromise();
}).catch( this.handleError);
login(data , urlKey): Observable<any>{
return this.http.post(CONFIG.url[urlKey], data)
.map( (res: Response) => this.saveUserInfo(res) ).catch( this.handleError)
}
saveUserInfo(res: Response): Response{
let userInfo = this.getUserInfo(res);
this.user = userInfo;
this.storage.set('user', userInfo);
return res;
}
The facebookLogin method goes like this. Access Facebook class login method which returns a promise. With information from the promise, I make http post request and save the returned data and then convert observable to promise with toPromise. In the test I spy on Facebook.login to return a resolving promise and spyOn http.post to return a successful observable. This is working fine in my app.But I am unable to run the test as it give the following error.
Error: Timeout - Async callback was not invoked within timeout specified by jasmine.DEFAULT_TIMEOUT_INTERVAL.
The code runs fine till the last point in http.post.map but then is not being run in the test. I think the problem is with the toPromise in the service.
Any kind of hep would be appreciated.
From my limited knowledge on Observable , I believe the problem with the approach was due to the fact that toPromise didnt get the value from observer.next(data). I assume subscription is necessary for that. The simple approach with Observable.of worked for me. You can import it from import 'rxjs/add/observable/of'

Angular return promise from httpBackend.when()

How do you return a promise from httpBackend.when()? I wanted to load some canned data stored in a .json file and return that from httpBackend.whenGET(). When I try to return the promise from http.get('mydata.json') the response is returned to the failure callback of the factory.
function getAvailablePackagesComplete(response) {
return response.data;
}
function getAvailablePackagesFailed(error) { // {error = Object {data: undefined, status: 0, config: Object, statusText: ""}
$log.error(error.data.description);
return false;
}
function getAvailablePackages() {
return $http.get('/1.0/get-available-packages')
.then(getAvailablePackagesComplete)
.catch(getAvailablePackagesFailed)
}
var data = {"package": "test", "version": "1"}
$httpBackend.whenGET('/1.0/get-available-packages').respond(function(method, url, data) {
// return [200,data, {}] // this works
return $http.get('app/home/fixtures/mydata.json'); // contains {"package: "test", "version": "1"}
}); //this doesn't work
As it is currently, $httpBackend (from ngMockE2E) does not support promises within its .respond - See AngularJS GitHub Issue #11245. As $httpBackend should be used to avoid making real HTTP requests, but you could let some requests pass through.
From AngularJS Docs:
This implementation can be used to respond with static or dynamic responses via the when api and its shortcuts (whenGET, whenPOST, etc) and optionally pass through requests to the real $httpBackend for specific requests (e.g. to interact with certain remote apis or to fetch templates from a webserver).
To work around what you're trying to do though, you could try to have getAvailablePackages() return the HTTP GET for your json file path and defining an $httpBackend.whenGET('pathTo.json').passThrough();
I was hitting the same issue and my use case was building a mock of my entire API in JS so that other people could work off line and develop the UI.
To achieve that I have developed a plugin called angular-mocks-async which decorates the httpBackend and adds the .whenAsync( ) APi to it. Than you can easily mock responses and return promises like so:
var app = ng.module( 'mockApp', [
'ngMockE2E',
'ngMockE2EAsync'
]);
app.run( [ '$httpBackend', '$q', function( $httpBackend, $q ) {
$httpBackend.whenAsync(
'GET',
new RegExp( 'http://api.example.com/user/.+$' )
).respond( function( method, url, data, config ) {
var re = /.*\/user\/(\w+)/;
var userId = parseInt(url.replace(re, '$1'), 10);
var response = $q.defer();
setTimeout( function() {
var data = {
userId: userId
};
response.resolve( [ 200, "mock response", data ] );
}, 1000 );
return response.promise;
});
}]);
You can return promises from http interceptors. Here is an example of delaying an HTTP call for 1 second. You can add this in your app.config(....)
$httpProvider.interceptors.push(function($q, $timeout) {
return {
'response': function(response) {
var defer = $q.defer();
$timeout(function() {
defer.resolve(response);
}, 1000);
return defer.promise;
}
};
});

Dynamic URL queries with AngularJS and NodeJS, MongoDB

I really do not understand how to handle URLs with queries appended to it.
I have endpoints that accept several parameters like:
?max_length=50,
?min_length=1,
?active=true,
?only_today=true,
?etc...
Via AngularJS how can I set those value dynamically only if the user has checked for those values?
Actually I'm just building an object {} appending those parameters when the $scope is not null. But I don't think it is a good idea.
Same for NodeJS and MongoDB...
How can I get the correct object based on the query string on the URL?
What I'm doing here as well is to split up the URL and checking for the words, etc... I'm sure there is a better way and I can not find it in both documentations and wondering to bigger and bigger URL parameters it start to be hell.
I know this is a real low level question but I don't really understand how to handle it.
Thanks
You can use the $location service for that.
https://docs.angularjs.org/api/ng/service/$location
You can use $resource to easily map your endPoints to your services. You should map your params to what is expected in your api. And if you have conditional parameters, you need to handle undefined params in your backend and ignore these params. For mapping your endpoints in nodeJS check out Restify
For example:
angular.module("myApp", []).factory("myFactory", function($resource) {
var YourResource = $resource('/rest/yourResource');
var factory = {
retriveMyResources: function(paramsQuery) {
return YourResource.query(paramsQuery).$promise;
}
};
return factory;
}).controller("myCtrl", function($scope, myFactory) {
myFactory.retrieveMyResources({
maxLength: $scope.maxLength,
sortBy: $scope.sortBy
}).then(function(result) {
$scope.result = result
})
})
Your node server
//App.js you initialize your server, and include your route files
(function() {
var restify = require("restify");
var server = restify.createServer({
name: "test-server"
});
server.pre(restify.CORS());
server.use(restify.gzipResponse());
server.use(restify.acceptParser(server.acceptable));
server.use(restify.queryParser());
server.use(restify.bodyParser());
server.use(restify.jsonp());
require("./routes/your_resource_route.js")(server);
server.listen("1921", function() {
console.log('%s listening at %s environment: %s', server.name, server.url, process.env.NODE_ENV);
});
})();
Example Route file
module.exports = function(server) {
var yourResourceService = require("services/your_resource_service.js");
server.get("rest/yourResource",
function(req, res, next) {
return yourResourceService.findResources(req.params.maxLength, req.params.sortBy).then(function(resources) {
res.send(200, resources);
next();
}).catch(function(err) {
res.send(500, err);
next();
}).done();
}
);
}
And your service file
module.exports = function(app_context) {
var exampleService = {
findItems: function(maxLength, sortBy) {
var sortObject = {};
sortObject[sortBy || DEFAULT_SORT] = -1;
return Q(brandMongooseCollection.find({}).sort(sortObject).limit(maxLength || DEFAULT_MAX_LENGTH).lean().exec());
}
};
return exampleService;
};

Testing a database response in an Angularjs/Jasmine test

I'm attempting to unit test an angular factory in jasmine but I'm having trouble understanding how to get an actual response from my database.
I have the following factory code that retrieves an object containing company information based on a company ticker value.
The factory works fine but I'd like to test it in jasmine.
app.factory('adminOps',function($http, $log, $q){
return {
getByTicker: function(ticker){
return $http.post("http://localhost:3000/ticker", {"ticker": ticker})
.then(function(response){
if (typeof response.data === 'object') {
return response.data;
} else {
return $q.reject(response.data);
}
}, function(response) {
return $q.reject(response);
});
}
};
});
To test this I have the following jasmine code based on online examples I found.
describe('adminOps', function() {
var factory, http;
beforeEach(module('myApp'));
beforeEach(inject(function(_adminOps_, $httpBackend) {
factory = _adminOps_;
http = $httpBackend;
}));
it('Should retrieve company data', function(done) {
var testCompany = function(company) {
expect(company.name).toBe("Google Inc.");
};
var failTest = function(error) {
expect(error).toBeUndefined();
}
http.expectPOST('http://localhost:3000/ticker',{ticker:"GOOG"}).respond(200, {ticker:"GOOG"});
factory.getByTicker("GOOG")
.then(testCompany)
.catch(failTest)
.finally(done);
http.flush();
});
});
I get a Expected undefined to be 'Google Inc.'.
How can I call my factory and test for the correct name value for the ticker parameter I send?
UPDATE: Koen's code works correctly. I've found if you want to test values on a server, like a rest api, then you should try something like frisby, which is built on Jasmine.
Unittests should test your local code and should not have external dependencies. Therefor your test doesn't and shouldn't actually access your database.
$httpBackend allows you to mock the http request and response.
Your code mocks the $httpBackend as follows:
http.expectPOST('http://localhost:3000/ticker',{
ticker:"GOOG"
}).respond(200,{
ticker:"GOOG"
});
meaning it will respond with a response of
{ticker:"GOOG"}
So it doesn't have the 'name' attribute you need.
A proper way to test your 'getByTicker' method is with the following $httpBackend setup:
$http.expectPOST('http://localhost:3000/ticker', {
ticker: "GOOG"
}).respond(200, {
name: "Google Inc."
});

Resources