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);
Related
Sample code:
if (!this.scope.popupHtmlTemplate) {
this.$http.get("widgets/pinpointcomponent/browseLibraries/resources/browseLibrariesDialogModal.html")
.success((data: any) => {
console.log("Inside success"+data)
if (data) {
this.generateBootBoxDialog(data);
this.scope.popupHtmlTemplate = data;
}
});
} else {
this.generateBootBoxDialog(this.scope.popopupHtmlTemplate)
}
Testcase:
it("test expandLibrary case 3", () => {
spyOn(http, "get");
controller.showBrowseLibrariesDialog();
expect(http.get).toHaveBeenCalled();
});
Getting
TypeError: undefined is not a constructorevaluating 'spyOn(http, "get")) error.
Any possible ways to write a testcase for this.
For one you're spying on an undefined http service and not $http. To dig deeper, you want to be mocking those $http calls with $httpBackend from ngMocks instead.
AngularJS: API: $httpBackend
This not only allows you to tell the test to expect an $http call methods like $httpBackend.expectGET(); but it allows you to mock calls and force digests with $httpBackend.flush(); and even mock the response.
Here, _fact is a reference to the service.
it('Git Check', function() {
$scope.user = 'swayams'
var data;
_fact.Git($scope).then(function(d) {
expect(d.data.length).toEqual(4)
}, function() { expect(d).not.toBeNull(); });
});
I am getting the error
SPEC HAS NO EXPECTATIONS Git Check
Update
After forcing async as per #FelisCatus and adding $formDigest, I am getting a different error Error: Unexpected request: GET https://api.github.com/users/swayams/repos
No more request expected
The updated code snippet looks something like -
it('Git Check', function(done) {
$scope.user = 'swayams'
var data;
_fact.Git($scope).then(function(d) {
expect(d.data.length).toEqual(4)
}, function() { expect(d).not.toBeNull(); });
});
$rootScope.$formDigest();
I have a Plunk here illustrating the issue.
Jasmine is not seeing your expectations because your function returns before any expect() is called. Depending on your situation, you may want to use async tests, or use some promise matchers.
With async tests, you add an additional argument to your test function, done.
it('Git Check', function (done) {
$scope.user = 'swayams'
var data;
_fact.Git($scope).then(function(d) {
expect(d.data.length).toEqual(4);
}, function() { expect(d).not.toBeNull(); }).finally(done);
$rootScope.$digest();
});
(Note the finally clause in the end of the promise chain.)
Please note that you have to do $rootScope.$digest() for the promises to resolve, even if your code is not using it. See: How to resolve promises in AngularJS, Jasmine 2.0 when there is no $scope to force a digest?
JSON/XML from REST
{
litm: "T00000245",
lotn: "00004"
}
<jdeSerials>
<litm>T00000245</litm>
<lotn>00004</lotn>
</jdeSerials>
AngularJS controller
//Searching a product with serial number/LOTN
$scope.searchProduct = function () {
var lotn = $scope.jdeSerials.lotn;
console.log("searchProduct---->" + lotn);//log-->searchProduct---->00004
$scope.JdeSerials = lotnService.get({id: lotn}, function() {
console.log($scope.jdeSerials);//log-->[object Object]
console.log($scope.jdeSerials.litm);//log-->undefined!!!!!
});
//var litm = $scope.jdeSerials.litm;
//$scope.jdeproduct = productService.get({id: litm});
};
AngularJS service
angular.module('lotnService', ['ngResource'])
.factory('lotnService', ['$resource',
function ($resource) {
console.log('------lotmService-----');
return $resource(
'http://localhost:8080/RMAServer/webresources/com.pako.entity.jdeserials/:id',
{},
{
update: { method: 'PUT', params: {id: '#lotn'} }
});
}]);
Question
How can I get a value to $scope.jdeSerials.litm? Is there any better idea to solve this like creating a service which handles this two GETs? I think that reason is the GET method is asynchronous, but what is the best solution to handle situations like this?
EDIT/update
I changed the service call like this:
$scope.JdeSerials = lotnService.get({id:lotn})
.$promise.then(function(jdeSerials) {
$scope.jdeSerials = jdeSerials;
console.log("1--------------->LITM:"+$scope.jdeSerials.litm);
});
I got the LITM, BUT I got the errormessage as well:
TypeError: Cannot read property 'then' of undefined
Try to create a get method in your resource.
angular.module('lotnService', ['ngResource'])
.factory('lotnService', ['$resource', function ($resource) {
return $resource( 'http://localhost:8080/RMAServer/webresources/com.pako.entity.jdeserials/:id',
{},
{
get: { method: 'GET', params: {id: '#lotn'}},
update: { method: 'PUT', params: {id: '#lotn'} }
});
}]);
Then in your controller, call method get from service:
lotnService.get({id:lotn}).$promise.then(
function(jdeSerials) {
$scope.jdeSerials = jdeSerials;
console.log("1--------------->LITM:"+$scope.jdeSerials.litm);
});
What angular js version are you using?
Does the following work ?
lotnService.get({id:lotn}).then(
function(jdeSerials) { ... }
);
(without the $promise)
I was browsing the docs and also angular-resource.js source for previous versions and it appears that the synthax has changed somewhere along the line.
On angular-resource.js 1.2.0 source:
The Resource instances and collection have these additional properties:
$promise: the {#link ng.$q promise} of the
original server interaction that created this * instance or
collection.
On 1.0.8 there is no mention of the $promise propery, however.
In my express app, when the DELETE method below is called, the GET method is immediately called after and it's giving me an error in my angular code that says it is expected an object but got an array.
Why is my GET method being called when i'm explicitly doing res.send(204); in my DELETE method and how can I fix this?
Server console:
DELETE /notes/5357ff1d91340db03d000001 204 4ms
GET /notes 200 2ms - 2b
Express Note route
exports.get = function (db) {
return function (req, res) {
var collection = db.get('notes');
collection.find({}, {}, function (e, docs) {
res.send(docs);
});
};
};
exports.delete = function(db) {
return function(req, res) {
var note_id = req.params.id;
var collection = db.get('notes');
collection.remove(
{ _id: note_id },
function(err, doc) {
// If it failed, return error
if (err) {
res.send("There was a problem deleting that note from the database.");
} else {
console.log('were in delete success');
res.send(204);
}
}
);
}
}
app.js
var note = require('./routes/note.js');
app.get('/notes', note.get(db));
app.post('/notes', note.create(db));
app.put('/notes/:id', note.update(db));
app.delete('/notes/:id', note.delete(db));
angularjs controller
$scope.delete = function(note_id) {
var note = noteService.get();
note.$delete({id: note_id});
}
angularjs noteService
angular.module('express_example').factory('noteService',function($resource, SETTINGS) {
return $resource(SETTINGS.base + '/notes/:id', { id: '#id' },
{
//query: { method: 'GET', isArray: true },
//create: { method: 'POST', isArray: true },
update: { method: 'PUT' }
//delete: { method: 'DELETE', isArray: true }
});
});
** UPDATE **
To help paint the picture, here's the angular error i'm getting:
Error: [$resource:badcfg] Error in resource configuration. Expected response to contain an object but got an array http://errors.angularjs.org/1.2.16/$resource/badcfg?p0=object&p1=array
I'm assuming that i'm getting this error because my delete method is calling my get method (somehow) and the get method returns the entire collection.
Server side
You're removing an element from a collection in your delete function. This is done asynchronously and calling your callback when it's finished.
During this time, other requests are executed, this is why your GET request is executed before your DELETE request is finished.
The same happens in your get function, you're trying to find an element from a collection and this function is too asynchronous.
But this is server side only and it is fine, it should work this way, your problem is located client side.
Client side
If you want to delete your note after you got it, you will have to use a callback function in your angular controller which will be called only when you got your note (if you need help on that, show us your noteService angular code).
This is some basic javascript understanding problem, actions are often made asynchronously and you need callbacks to have an execution chain.
Maybe try doing something like this:
$scope.delete = function(note_id) {
var note = noteService.get({ id: note_id }, function()
{
note.$delete();
});
}
Your code doesn't make sense though, why is there a get in the $scope.delete? Why not do as simply as following:
$scope.delete = function(note_id) {
noteService.delete({ id: note_id });
}
Error
I think you get this error because of what your server sends in your exports.delete function. You're sending a string or no content at all when angular expects an object (a REST API never sends strings). You should send something like that:
res.send({
results: [],
errors: [
"Your error"
]
});
I have recently decided to use $resource for REST service, but I quickly got in trouble. I have a simple URL:
/r/interface/activate/:active/addr/:address
But calling it in unit test make it fail with:
// expected: POST /r/interface/activate/false/addr/123
// got : POST /r/interface/activate/false/addr/#address
My defined resource is :
module.factory('NetworkInterfaceActivationService', function ($resource) {
return $resource("/r/interface/activate/:active/addr/:address", {}, {
activate:{method:'POST', params:{active:"true", address:'#address'}, isArray:false},
deactivate:{method:'POST', params:{active:"false", address:'#address'}, isArray:false}
});
});
And this is how I call my service !
$scope.deactivateNetworkAddress = function (address) {
NetworkInterfaceActivationService.deactivate({address:address});
};
Am I missing something?
I tried your code with Angular 1.3, but I couldn't reproduce the problem.
jsFiddle
<button ng-click="parentCtrl.doIt('123foobar')">execute</button>
app.controller('ParentCtrl', function (NetworkInterfaceActivationService) {
this.doIt = function (address) {
NetworkInterfaceActivationService.deactivate({address:address});
};
})
app.factory('NetworkInterfaceActivationService', function ($resource) {
return $resource("/r/interface/activate/:active/addr/:address", {}, {
activate:{method:'POST', params:{active:"true", address:'#address'}, isArray:false}
});
});
HTTP call in console:
POST .../r/interface/activate/false/addr/123foobar