I am new to Jasmine. I am trying to get my factory promise to resolve but it never seems to.
Here is my factory which will retuirn a list of companies
angular.module('commercial.factories').factory('companyFactory', companyFactory);
function companyFactory($http, $location, sessionStorage, config) {
function getCompanies() {
//call service and include company identifier in the service url
return $http.get(wunUtils.constants.serviceBaseUrl + 'myCompanyService.svc/companies')
.then(function(response) {
return response.data;
}, function(err){
console.log(err);
});
}
});
and my spec
describe('company', function() {
beforeEach(module('mockedCommercial'));
var $httpBackend, companyFactory, $rootScope
beforeEach(function() {
inject(function(_$httpBackend_, _$rootScope_, _companyFactory_) {
$rootScope = _$rootScope_;
$httpBackend = _$httpBackend_;
companyFactory = _companyFactory_;
});
})
it('should return available languages asynchronously', function() {
$httpBackend.expectGET(/https:\/\/myDomain.com\/myCompanyService.svc\/companies+/)
.respond(function(method, url, data, headers, params) {
var response = {
"data": [{
"address1": "Portsmouth",
"address2": null,
"city": "Portsmouth",
"contactEmail": "mocks#mocks.com",
"contactFirstName": "Mock",
"contactLastName": "Data",
"contactPhone": null,
"contactTitle": "Mr",
"country": "UK",
"fax": null,
"identifier": "c1xxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
"location": "Portsmouth, UK",
"name": "Mock Company 1",
"phone": null,
"state": "Hampshire",
"zipCode": "PO19 3 EN"
}, {
"address1": "Portsmouth",
"address2": null,
"city": "Portsmouth",
"contactEmail": "mocks#mocks.com",
"contactFirstName": "Test",
"contactLastName": "Test",
"contactPhone": null,
"contactTitle": "Mrs",
"country": "UK",
"fax": null,
"identifier": "c2xxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
"location": "Portsmouth, UK",
"name": "Mock Company 2",
"phone": null,
"state": "Hampshire",
"zipCode": "Po9 3EN"
}],
"total": 2
}
});
//--------------------------
$rootScope.$digest();
companyFactory.GetCompanies().then(function(result) {
expect(result.hasOwnProperty('total')).toBe(true);
})
});
});
As you can see i am using $rootScope.$digest to run the promise, however my expect is never hit so the test always passes.
I have read loads of different posts about this who all say this should work. Am i doing something wrong here or have i missed some step?
Edited - Simplified approach
So i simplified my question somewhat after looking at some more posts
Here is my new controller call...
$scope.companies = [];
var GetCompanies = function(options) {
companyFactory.GetCompanies().then(function(response) {
options.success(response);
$scope.companies = response
});
}
$scope.GetCompanies = GetCompanies
my factory...
function getCompanies() {
//call service and include company identifier in the service url
return $http.get(wunUtils.constants.serviceBaseUrl + 'CompanyService.svc/companies')
.then(function(response) {
return response.data;
}, function(err){
console.log(err);
});
}
var service = {
AddCompany: addCompany,
GetCompany: getCompany,
GetCompanies: getCompanies,
GetUserCompanyFleets: getUserCompanyFleets,
EditCompany: editCompany
};
and my new spec
describe('company', function() {
beforeEach(module('mockedCommercial'));
var $httpBackend, companyFactory, $rootScope, createController, scope
beforeEach(function() {
inject(function($controller, _$httpBackend_, _$rootScope_, _companyFactory_) {
$rootScope = _$rootScope_;
$httpBackend = _$httpBackend_;
scope = _$rootScope_.$new();
companyFactory = _companyFactory_;
createController = function() {
return $controller('ManageCompaniesController', {
'$scope': scope
});
};
});
})
it('should return available languages asynchronously', function(done) {
$httpBackend.whenGET(/https:\/\/myDomain.com\/V3_Fleet\/CompanyService.svc\/companies+/)
.respond(function(method, url, data, headers, params) {
return [{ 'id': 1 }];
}
});
//--------------------------
var dummy = [{ 'id': 1 }];
createController();
scope.GetCompanies()
$httpBackend.flush()
expect(scope.companies).toEqual(dummy)
scope.$digest(); done()
});
});
Try using $httpBackend.flush() before evaluating the test expectations.
This is the approach recommended by angular. For more details and examples, you can check the documentation here.
You have two options when testing asynchronous code.
1. Use synchronous mocks for everything
This includes:
mock $httpBackend
use $timeout.flush()
use $interval.flush()
use scope.$apply() to process leftover promises
And after all these you still cannot be entirely sure that all pending tasks have been processed, although its sufficient in 99.9% of the time.
2. Make your tests asynchronous
Here you will have to make sure that every asynchronous controller method returns a promise, even if the returned promise is not used in the application. They will be used in tests.
Jasmine makes async testing pretty difficult but not impossible. You will have to use the done callback, and code every asynchronous block like:
it('should...', function(done) {
$q.when().then(function() {
return $controller.GetSomething();
}).then(function() {
expect(scope.companies).toEqual(dummy);
}).then(done, done.fail);
});
Mocha handles proper asynchronous functions so you can write without done:
it('should...', function() {
return $q.when().then(function() {
return $controller.GetSomething();
}).then(function() {
expect(scope.companies).toEqual(dummy);
});
});
Or even use async-await if you have the toolset:
it('should...', async () => {
await $controller.GetSomething();
expect(scope.companies).toEqual(dummy);
});
So after much reading i found the answer by simplifying everything down. I think my issues came from mixing $httpBackend.flush() and the done() function
What seems to happen is the the done() function triggers its own digest, so when trying to use the flush() function there is a conflict.
Here is my working factory. Its a little different from my original purly as this was a more simple function to work with (less things to go wrong)
function getUser(userId) {
//call service and include user identifier in the service url
return $http.get(wunUtils.constants.serviceBaseUrl + 'myService.svc/fleetusers/' + userId)
.then(function(response) {
return response.data;
});
}
var service = {
GetMe: getMe,
GetUser: getUser,
AddUser: addUser,
EditUser: editUser,
ActivateUser: activateUser
};
and here is my simplified spec call the $httpBackend.Flush()
describe('Testing httpBackend', function() {
var $httpBackend;
beforeEach(module('mockedCommercial'));
beforeEach(inject(function(_$httpBackend_) {
$httpBackend = _$httpBackend_;
}))
it('should behave...', function() {
inject(function(userFactory, $rootScope) {
var mockData = [];
$httpBackend.expectGET(wunUtils.constants.serviceBaseUrl + 'FleetUserService.svc/fleetusers/10').respond(mockData);
var promise = userFactory.GetUser(10);
$httpBackend.flush();
promise.then(function(result) {
expect(result).toEqual(mockData);
});
});
});
});
EDIT
It should also be noted that when testing HTTPBackend the test will only work while using the expectGET and expectPOST as opposed to whenGET and whenPOST.
From what i can see, whenGet is used for mocking data while expectGet is used for testing mocks
Related
I have simple RESTful server with Flask and I like to make a simple client with AngularJS using ngResource. The idea is make a GET to the server, and obtain a json file.
This is my services.js
var IpZapServices = angular.module('IpZapServices', ['ngResource']);
IpZapServices.factory('Plug', ['$resource', function($resource) {
return $resource('http://localhost:8003/api/plugs/:id',
{id : "#id"}, {
query: {method: 'GET', params: {}, isArray: false}
});
}]);
And the controllers.js
var IpZapControllers = angular.module('IpZapControllers', []);
IpZapControllers.controller('PlugListCtrl', ['$scope', 'Plug', function($scope, Plug) {
$scope.plugs = Plug.query();
console.log($scope.plugs);
}]);
But, I don't get the json file, get this:
Object { $promise: Object, $resolved: false }
Why? What's I do wrong? Thanks!
EDIT:
This is the raw response that I receipt from the server.
{"plugs": [
{"alarm": [],
"id": 0,
"name": "Plug 0",
"state": false},
.
.
.
{"alarm": [],
"id": 3,
"name": "Plug 3",
"state": false}
]}
EDIT 2: Solution
The problem is in the server. Simply add to the server Flask-CORS and work!
from flask.ext.cors import CORS
app = Flask(__name__)
cors = CORS(app, resources={r"/api/*": {"origins": "*"}})
The solution is from this question
You need to resolve the promise you have created with Plug.query()
Try this:
Plug.query().$promise.then(function (response) {
console.log(response)
$scope.plugs = response;
});
More information on this can be found in the docs on angularjs.org:
https://docs.angularjs.org/api/ngResource/service/$resource
Try this:
var IpZapControllers = angular.module('IpZapControllers', []);
IpZapControllers.controller('PlugListCtrl', ['$scope', 'Plug', function($scope, Plug) {
$scope.plugs = Plug.query();
$scope.plugs.$promise.then(function (result) {
console.log(result);
$scope.plugs = result;
});
}]);
Explanation:
Your call to query will not return the data immediately. It instead returns a promise object that will eventually get your data once the HTTP request is complete (notice that the console message said resolved: false).
Read More:
Promises in AngularJS, Explained as a Cartoon
It seems that you have a promise there.
Don't assign the return value of the function, you need to wait till the promise resolves or rejects
Try
Plug.query().$promise
.then(function (response) {
// success code here
})
.catch(function (err) {})
Whwn connecting to a webapi from the controller, use the success and error promise from the method call.
Ctrl.$inject = ["$scope", "Service"];
function Ctrl($scope, Service) {
Service.getJson().success(function (data) {
$scope.json = data;
})
.error(function (data) {
// data should show the error
});
}
I am configuring an AngularJS app and am having a little trouble ensuring that my promises are firing before the controllers are loading. My understanding is that this can be done. A bit of code:
First, here's the router code:
$routeProvider
.when('/apage', {
controller: 'APageController',
templateUrl: 'app/apage/apage.view.html',
requiresLogin: true,
resolve: {
"data": function($q, data1Service, data2Service) {
var data1 = data1Service.getData();
var data2 = data2Service.getData();
return $q.all({
data1: data1.$promise,
data2: data2.$promise});
}
}
})
...
Here's one of the service functions (both are similar)...
function getData() {
var deferred = $q.defer();
$http(req)
.success(function(data, status, headers, config) {
// store data ...
deferred.resolve(1); // just a flag to say it worked
$rootScope.$apply();
})
.error(function(data, status, headers, config) {
deferred.resolve(0);
$rootScope.$apply();
});
return deferred.promise;
}
And here's the controller code...
angular
.module('app')
.controller('APageController', APageController);
APageController.$inject = [... 'data'];
function APageController(... data) {
var data1 = data.data1;
var data2 = data.data2;
...
All three are in different files but part of the same module. There must be some concept I'm overlooking. Is there anything apparent here to explain why my resolve promises are not firing?
Thank you.
If you pass in object literal to q.all it will resolve immediately. Instead pass array of promises so that it waits for it to resolve reject. Also .$promise is not needed in your case because you are already returning a promise from your service(based on the displayed code). $promise is generally attached by the object returned by $resource
i.e
return $q.all([data1Service.getData(),
data2Service.getData()]);
and expect data to be array of data resolved from above 2 calls.
or create 2 resolves:
resolve: {
"data": function($q, data1Service, data2Service) {
return data1Service.getData();
},
"data2": function($q, data1Service, data2Service) {
return data2Service.getData();
}
}
and inject data and data2.
otherwise, chain through and change the response format to expect the way you were originally trying to get.
resolve: {
"data": function($q, data1Service, data2Service) {
return $q.all([data1Service.getData(),data2Service.getData()])
.then(function(response){
return {
data1:response[0],
data2:response[1]
};
});
}
}
Do not place rootScope.apply inside your service, it will cause digest already in progress error (since angular will ). $http will automatically resolve it.
You just need this.
function getData() {
return $http(req);
}
I'm trying to write a factory which exposes a simple users API. I'm new to AngularJS and I'm a bit confused about factories and how to use them. I've seen other topics but none that are a good match to my use case.
For the sake of simplicity, the only functionality I'd like to achieve is getting all users in an array and then pass them to a controller through the injected factory.
I stored the users in a json file (for now I only want to read that file, without modifying the data)
users.json:
[
{
"id": 1,
"name": "user1",
"email": "a#b.c"
},
{
"id": 2,
"name": "user2",
"email": "b#b.c"
}
]
The factory I'm trying to write should be something like this:
UsersFactory:
app.factory('usersFactory', ['$http', function ($http) {
return {
getAllUsers: function() {
return $http.get('users.json').then(
function(result) {
return result.data;
},
function(error) {
console.log(error);
}
);
}
};
}]);
And finally, the controller call would be like this:
UsersController
app.controller('UsersCtrl', ['$scope', 'usersFactory', function($scope, usersFactory){
usersFactory.getAllUsers().then(function (result) {
$scope.users = result;
});
}]);
I've searched the web and it seems like it is not really a good practice to use factories this way, and if I'd like to achieve some more functionality like adding/removing a new user to/from the data source, or somehow store the array within the factory, that wouldn't be the way to do it. I've seen some places where the use of the factory is something like new UsersFactory().
What would be the correct way to use factories when trying to consume APIs?
Is it possible to initialize the factory with an object containing the $http.get() result and then use it from the controller this way?
var usersFactory = new UsersFactory(); // at this point the factory should already contain the data consumed by the API
usersFactory.someMoreFunctionality();
I don't see anything wrong with your factory. If I understand correctly you want to add functionality. A few small changes would make this possible. Here's what I'd do (note that calling getAllUsers wipes out any changes):
app.factory('usersFactory', ['$http', function ($http) {
var users = [];
return {
getAllUsers: function() {
return $http.get('users.json').then(
function(result) {
users = result.data;
return users;
},
function(error) {
users = [];
console.log(error);
}
);
},
add: function(user) {
users.push(user);
},
remove: function(user) {
for(var i = 0; i < users.length; i++) {
if(users[i].id === user.id) { // use whatever you want to determine equality
users.splice(i, 1);
return;
}
}
}
};
}]);
Typically the add and remove calls would be http requests (but that's not what you're asking for in the question). If the request succeeds you know that your UI can add/remove the user from the view.
I like my API factories to return objects instead of only one endpoint:
app.factory('usersFactory', ['$http', function ($http) {
return {
getAllUsers: getAllUsers,
getUser: getUser,
updateUser: updateUser
};
function getAllUsers() {
return $http.get('users.json');
}
function getUser() {
...
}
function updateUser() {
...
}
}]);
That way if you have any other user-related endpoints you can consume them all in one factory. Also, my preference is to just return the $http promise directory and consume the then() in the controller or where ever you're injecting the factory.
I'm really a fan of route resolve promises. Here is John Papa's example. I will explain afterwards how to apply this to what you're doing:
// route-config.js
angular
.module('app')
.config(config);
function config($routeProvider) {
$routeProvider
.when('/avengers', {
templateUrl: 'avengers.html',
controller: 'Avengers',
controllerAs: 'vm',
resolve: {
moviesPrepService: moviesPrepService
}
});
}
function moviesPrepService(movieService) {
return movieService.getMovies();
}
// avengers.js
angular
.module('app')
.controller('Avengers', Avengers);
Avengers.$inject = ['moviesPrepService'];
function Avengers(moviesPrepService) {
var vm = this;
vm.movies = moviesPrepService.movies;
}
Basically, before your route loads, you get the request data you need (in your case, your "users" JSON.) You have several options from here... You can store all that data in a Users factory (by the way, your factory looks fine), and then in your controller, just call Users.getAll, which can just return the array of users. Or, you can just pass in users from the route resolve promise, much like John Papa does in his example. I can't do it as much justice as the article he wrote, so I would seriously recommend reading it. It is a very elegant approach, IMHO.
I typically use a factory something like this:
.factory('usersFactory', ['$resource',
function($resource){
return $resource('http://someresource.com/users.json', {}, {
query: {
method:'GET',
isArray:true
}
})
}])
Which you could call with:
usersFactory.query();
As this is a promise you can still use the .then method with it too
$http is a promise that means you have to check whether your get call worked or not.
so try to implement this type of architecture in your controller
$http.get('users.json')
.success(function(response) {
// if the call succeed
$scope.users = result;
})
.error(function(){console.log("error");})
.then(function(){
//anything you want to do after the call
});
I have tried to create a mock of my BoardService. However, the .then function is not being run in the controller when running the test, but it's working fine when running the actual application.
Here is the test:
beforeEach(inject(function($rootScope, $controller, $q) {
scope = $rootScope.$new();
BoardController = $controller('BoardController', {
$scope: scope,
board: {
id: 1,
tasks: []
},
BoardService: {
addTask: function(data) {
var defer = $q.defer();
defer.resolve(1);
return defer.promise;
}
}
});
}));
it("should add a new task", function() {
scope.addTask(category, 'this is a new task');
expect(scope.board.tasks.length).toBe(1);
});
And the controller function:
$scope.addTask = function(category, task) {
BoardService.addTask({name: task, category: category.id, boardId: $scope.board.id}).then(function(task_id) {
// Never reaches here
$scope.board.tasks.push({id : task_id, name : task, category : category.id, board_id : $scope.board.id});
});
}
Why is it never reaching the comment in the .then function?
After resolving the promise you need to trigger a digest-cycle for angular to pick up on it.
scope.addTask(category, 'this is a new task');
scope.$digest();
expect(scope.board.tasks.length).toBe(1);
As we discussed on IRC: you need to call scope.$digest for the promise to fire.
i'm a biginner when it comes to Angular.js, and i have a problem with $scope not getting additional value from one of two $resource rest calls. Here's my code:
controller: function ($scope, $modalInstance, $route) {
$scope.server = {}
$scope.submit = function () {
//AddNewServer is a $resource service
AddNewServer.post($.param({
'name': $scope.server.name,
'ip': $scope.server.ip,
'port': $scope.server.port
}));
//ServerStats is a $resource service
ServerStats.postServerStats(function success(data) {
$scope.server.bytesIn = data.returnValue.bytesIn
}, function err(err) {
console.log("Error: " + err)
})
$modalInstance.dismiss('cancel');
$route.reload()
//BELLOW LOG RETURNS Object {name: "asd", ip: "asd", port: 2} NO bytesIn
console.log($scope.server)
}
$scope.cancel = function () {
$modalInstance.dismiss('cancel');
$route.reload()
};
}
Question is how do i add bytesIn from my other service call into my server object? I'm sure it a pretty obvious thing but i'm still in learning phase. Thanks in advance.
Your postServerStats() call is asynchronous, so it's likely that your success function isn't being called before the console.log($scope.server) statement.
Put console.log($scope.server) in your success function, after you assign $scope.server.bytesIn.
Perhaps you mean to do more work in your postServerStats() callback?
Or better yet, look into angular promises