I'm new to angular unit test cases and I'm trying to write a unit test case for the following code that I have in my Angular controller. Data contains balance which is added up and assigned to $scope.Total
.success(function (data) {
$scope.Total = _.reduce(data, function(memo, value) {
return memo + value.Balance;
}, 0);
});
My test case looks as follows
it('should return Total', function () {
_http.whenGET('').respond([{
data: [
{ Balance: 5 },{Balance:10}]
}]);
_controller();
_http.flush();
expect(_scope.Total).toBe(15);
});
I keep getting the error Expected NaN to be 15.Error: Expected NaN to be 15.
Please let me know what I'm doing wrong here. Would be great if you can show me the correct code.
Thanks in advance.
I believe it is because of the incorrect data structure for the data stub in the httpBackend stubbing.
_http.whenGET('').respond([{
data: [
{ Balance: 5 },{Balance:10}]
}]);
Should be:
_http.whenGET('').respond([{ Balance: 5 },{Balance:10}]);
Looking at your implementation it looks like you are looking for array of objects with each object having balance property. Instead you are adding one more layer in the data structure.
Plnkr
A side note: You may want to abstract http calls to angular service and inject angular service in the controllers instead of directly using http. With that you achieve separation of concerns and single responsibility in your controller. Also you can easily create mocks for your service as well.
Related
I have the following test case:
it('should return id if the post is successful',function(){
var result = {
id : "123"
};
ctrl.saveCallback(result);
expect(ctrl.method.id).to.equal("123");
});
Where ctrl.saveCallback copies the result.id into the method.id on the ctrl and then shows the success banner. On the success banner, we are using the translate filter to translate the message before showing it.
Function:
.....
ctrl.method.id = result.id;
magicallyShowOnScreen($filter('translate')('MESSAGES.SUCCESS'));
....
magicallyShowOnScreen is a service that shows whatever string we pass onto the screen, and that has been injected into beforeEach.
Can someone please point in the right direction as to how should I test or mock out this $filter('translate') ?
Preface: I'm not familiar with Mocha.js or how one would create spies however you would still inject them or similar mock objects the same way as you would for other testing frameworks.
Below is a Jasmine example, I hope it helps.
When you bootstrap your module (using the module / angular.mocks.module) function, you should provide your own version of $filter which should be a mock / spy that returns another mock / spy. For example
var $filter, filterFn;
beforeEach(module('myModule', function($provide) {
$filter = jasmine.createSpy('$filter');
filterFn = jasmine.createSpy('filterFn');
$filter.and.returnValue(filterFn);
$provide.value('$filter', $filter);
});
Then, in your test, you can make sure $filter is called with the right arguments, eg
expect($filter).toHaveBeenCalledWith('translate');
expect(filterFn).toHaveBeenCalledWith('MESSAGE.SUCCESS');
expect(magicallyShowOnScreen).toHaveBeenCalled(); // assuming this too is a spy
I'm part of a team that's been working on angularjs for quite a while now and our unit tests consist of files which contain both the test logic and provider mocks of each component that tested element relies on.
This is leading to more and more code duplication, can anyone recommend an angular-ey method whereby we could maintain a test infrastructure, mock a controller or service once and be able to inject that mocked service into other tests as required?
Edit The code I'm working with is proprietary but for an example consider you have 10 or 15 services which retrieve data via HTTP requests, format the data in different ways and return that data. One example might return arrays of data so in each file which relies on the service we would have initialising code like the following ( cut down and altered so please ignore any typos or other mistakes )
myDataService: {
getDataParsedLikeY: {
[{obj1},{obj2}...]
},
getDataParsedLikeX: {
[{obja},{objb}...]
}
beforeEach(angular.mock.module('myModule'));
beforeEach(angular.mock.inject(function(myDataService) {
myDataService = function( functionName ) {
return myDataService[functionName];
}
spyOn(myDataService).and.callThrough();
})
}
If you are looking for a way not to declare same mock codes in every test files, I would suggest something like below although I'm not sure this is angular-way or not.
[1] write the code to declare your mock services like below only for unit test and import after angular-mocks.js
In your-common-mocks.js(you can name the file as you like)
(function(window){
window.mymock = {};
window.mymock.prepare_mocks = function(){
module(function($provide) {
$provide.service('myDataService', function(){
this.getDataParsedLikeY = function(){
return 'your mock value';
};
});
});
};
})(window);
If you use karma, you could write like this to import the file in karma.conf.js.
files: [
...
'(somedirctory)/angular-mocks.js',
'(somedirctory)/your-common-mocks.js'
...
]
[2] use mymock.prepare_mocks function in your test files.
describe('Some Test', function() {
beforeEach(mymock.prepare_mocks);
it('getDataParsedLikeY', inject(function(myDataService){
expect('your mock value', myDataService.getDataParsedLikeY());
As a result of above, you have to write your mock code just one time and share the mock code in your every test files. I hope this could suggest you something to achieve what you want.
If your jasmine version is 2.x, you can write test code like below without writing mock service.
spyOn(YourService, 'somemethod').and.returnValue('mock value');
In jasmine 1.3 you need to adjust little bit.
spyOn(YourService, 'somemethod').andReturn('mock value');
I hope this could help you.
I want to develop set of functions(sort of library) for CRUD in AngularJS so I can reuse them for couple of entities of my project. For server communication I made factory of $resource and using accordingly. $resource factory looks like this:
Model File:
var get_entity_model = angular.module("app.getentity", []).factory('getEntity', ['$resource', function($resource) {
return{
entity_view: $resource(baseurl+'/rest/'+serviceName+'/entity/:id/?app_name='+appName+'&fields=*', null, {'update': { method:'PUT' }})
}
}]);
And here how I'm using it in controller
Controller File:
getEntity.entity_view.get(
function(entity_list){
},
function(error){
}
)
Here entity_view is the table name. I'm passing all related functions like pagination and sub request to get the data of related tables etc code I put into success function of above request.
Now I want to make a library where I can define all this stuff and simply by calling the function I should be able to get all this stuff like:
entity.getEntity()
Should return same result as above code.
I tried with creating factory for above task but seems it need callback function and function at factory will return only data which I'm already getting from my model file so I need to make it compact and easy to use.
Factory Code at factory file:
var api = angular.module("app.entity_api", []).factory('entity_factory', ['$resource','getEntity',function($resource,getEntity) {
var entity_factory = {};
entity_factory.get_entity = function(callback){
getEntity.entity_view.get().$promise.then(
function(data){
callback(data.record);
}
);
}
return entity_factory;
}]);
And here how I call the function in controller:
Controller code:
api.controller("sample",['entity_factory','getEntity','$scope',function(entity_factory,getEntity,$scope){
$scope.init = function(){
entity_factory.get_entity(
function(data){
console.log(data);
}
);
}
$scope.init();
}])
Problem is that my entity_factory code will return only the data from server rest of the additional code I've to do in callback function which seems not much difference than my current exercise. So, the question is how can I achieve my goal to make a library of functions with additional code which return complete compiled result to make the code reusable for other entities and compact.
I like that you're a thinking of making a library but in this case, don't reinvent the wheel and save your precious time. Check out Restangular and your task will be a lot easier. Restangular is an AngularJS service that simplifies common GET, POST, DELETE, and UPDATE requests with a minimum of client code. It's a perfect fit for any WebApp that consumes data from a RESTful API.
WARNING: fairly long question
I am new to angular and I've gone through a few tutorials and examples such as the official tutorial on the angularjs website. The recommended way to get data into your app and share that data between controllers seems very clear. You create a service that is shared by all controllers which makes an asynchronous request to the server for the data in JSON format.
This is great in theory, and seems fine in the extremely simple examples that only show one controller, or when controllers don't share logic that depends on the shared data. Take the following example of a simple budget application based on yearly income and taxes:
Create the app with a dependency on ngResource:
var app = angular.module('budgetApp', ['ngResource']);
NetIncomeCtrl controller that handles income and tax items, and calculates net income:
app.controller('NetIncomeCtrl', function ($scope, BudgetData) {
var categoryTotal = function (category) {
var total = 0;
angular.forEach(category.transactions, function (transaction) {
total += transaction.amount;
});
return total;
};
$scope.model = BudgetData.get(function (model) {
$scope.totalIncome = categoryTotal(model.income);
$scope.totalTaxes = categoryTotal(model.taxes);
});
$scope.netIncome = function () {
return $scope.totalIncome - $scope.totalTaxes;
};
});
BudgetData service that uses $resource to retrieve the JSON data from the server:
app.factory('BudgetData', function ($resource) {
return $resource('data/budget.json');
});
budget.json contains the JSON data returned from the server:
{
"income": {
"transactions": [
{
"description": "Employment",
"amount": 45000
},
{
"description": "Investments",
"amount": 5000
}
]
},
"taxes": {
"transactions": [
{
"description": "State",
"amount": 5000
},
{
"description": "Federal",
"amount": 10000
}
]
},
}
Then on my screen I have two repeaters that show the income and tax items (which you can edit), and then the net income is calculated and displayed.
This works great, and is the standard approach I've seen used in tutorials. However, if I just add one more controller that depends on some of the same data and logic, it begins to unravel:
ExpensesCtrl controller for expenses which will in the end calculate the surplus (net income - expenses):
app.controller('ExpensesCtrl', function ($scope, BudgetData) {
var categoryTotal = function (category) {
var total = 0;
angular.forEach(category.transactions, function (transaction) {
total += transaction.amount;
});
return total;
};
$scope.model = BudgetData.get(function (model) {
$scope.totalIncome = categoryTotal(model.income);
$scope.totalTaxes = categoryTotal(model.taxes);
$scope.totalExpenses = categoryTotal(model.expenses);
});
$scope.netIncome = function () {
return $scope.totalIncome - $scope.totalTaxes;
};
$scope.surplus = function () {
return $scope.netIncome() - $scope.totalExpenses;
};
});
budget.json adds the expenses data:
"expenses": {
"transactions": [
{
"description": "Mortgage",
"amount": 12000
},
{
"description": "Car Payments",
"amount": 3600
}
]
}
Then on a separate part of the screen I have a section that uses this controller and uses a repeater to show the expense items, and then re-displays the net income, and finally shows the resulting surplus.
This example works, but there are several problems (questions) with it:
1) In this example I've managed to keep most of my controller's logic out of the callback function, but all controllers start out in a callback because everything depends on the model being loaded. I understand this is the nature of javascript, but angular is supposed to reduce the need for all of these callbacks, and this just doesn't seem clean. The only reason I was able to take some of the logic out of the callbacks here is because of the magic that angular does under the hood to substitute a fake object for the model until the model gets loaded. Since this 'magic' is not intuitive, it's hard to tell if your code will work as expected.
Is there a consistent way people deal with this? I really don't want some elaborate solution that makes this 101 intro app into something really complicated. Is there a simple and standard approach to restructuring this code somehow to avoid so many callbacks and make the code more intuitive?
2) I have a bunch of repeated logic. The categoryTotal and netIncome logic should only be in one place, and shared between controllers. These controllers are used in completely separate parts of the screen, so they can't use scope inheritance. Even when using scope inheritance there could be problems, because the child controller's scope can't depend on the parent scope's model being loaded even when its own model is loaded.
The categoryTotal logic is just a helper function that isn't tied directly to the model, so I don't know where you put generic helper functions in angular. As for the netIncome which does directly depend on the model and needs to be in the scope, it would be possible to add it to the service or the model. However, the service should only be concerned with retrieving the model from the server, and the model should just contain data, and be as anemic as possible. Also, it seems like this kind of logic belongs in a controller. Is there a standard way to deal with this?
3) Every time the service is called to fetch the data, it does an http request to the server, even though the data is the same every time. The model should be cached after the first request, so there is only one request made.
I realize I could handle this in the service. I could just store the model as a local variable and then check that variable to see if the data already exists before making the request. It just seems odd that none of the tutorials I read even mentioned this. Also, I'm looking for a standard 'angular' way of dealing with it.
Sorry for the incredibly long post. I would really like to keep this app at the 101 intro level, and not get into really complicated areas if possible. I'm also hoping there is a standard 'angular' way to deal with these problems that I have just not come across yet.
Thanks in advance!
This is how I do it. You create a service that handles data, and if it is changed it broadcasts messages to your controllers. It will get the initial data that can be gotten with BudgetData.data. If anyone changes the data
.service("BudgetData", function($http, $rootScope) {
var this_ = this, data;
$http.get('wikiArticles/categories', function(response) {
this_.set(response.data);
}
this.get = function() {
return data;
}
this.set = function(data_) {
data = data_;
$rootScope.$broadcast('event:data-change');
}
});
In your controller you just need to listen for the events, and it will update your scope variables accordingly. You can use this in as many controllers as you want.
$rootScope.$on('event:data-change', function() {
$scope.data = BudgetData.get();
}
$scope.update = function(d) {
BudgetData.set(d);
}
According to this Paweł Kozłowski's answer, Typeahead from AngularUI-Bootstrap should work when asynchronously obtaining popup items with $resource in newest Angular versions (I'm using 1.2.X).
Plunk - Paweł's version - Typeahead with $http
I guess I don't know how to use it properly (As a result I get an error in typeaheadHighlight directive's code - typeahead treats instantly returned Resources as strings and tires to highlight them).
Plunk - Typeahead with $resource
I think the critical code is:
$scope.cities = function(prefix) {
var p = dataProviderService.lookup({q: prefix}).$promise;
return p.then(function(response){
$log.info('Got it!');
return response.data;
});
return p;
};
I've tried bunch of stuff - returning $promise (version from Plunker), query(), then().
Currently, I'm using $http for this functionality in my app and I'm ok with it. Still, just wanted to know how to achieve the same with $resource.
You might want to take a look at this: https://github.com/angular/angular.js/commit/05772e15fbecfdc63d4977e2e8839d8b95d6a92d
is ui.bootstrap.typeahead compatible with those changes in $resource's promise API ?
Should be:
$scope.cities = function(prefix) {
return dataProviderService.lookup({q: prefix}).$promise.then(
function(response){
// might want to store them somewhere to process after selection..
// $scope.cities = response.data;
return response.data;
});
};
This is based of the angular commit mentioned above, and it worked for me on Angular 1.2.13
Thanks to the answer from #andrew-lank, I did mine with $resource as well. I didn't have a data attribute in my response though. I used the query method on $resource, which expects a responsetype of array so maybe that is why there is no data attribute. It is an array of Resource objects, each of which contains the data. So my code is slightly simpler, looks more like this:
$scope.cities = function(prefix) {
return dataProviderService.query({q: prefix}).$promise.then(
function(response){
return response;
});
};
I ran into this same problem and it had me banging my head against the wall. The problem is with the data service since it is returning an array of strings instead of wrapping them in a JSON object. $resource will not work with an array of strings.
For additional info, check out this answer:
One dimensional array of strings being parsed to 2d by angular resource
typeahead="i.t_UserName for i in employeeInfo | filter:$viewValue | limitTo:4"
goes as an attribute of your html input
and in your controller (using employee resource)
$scope.employeeInfo = getEmployeeResourceSvc.getEmplInfo.query(function(response){
$scope.employeeInfo= response;
});
In the ui-bootstrap 13.0 and angular 1.3, you can now do the following:
$scope.cities = function (q) {
return $scope.resource.query({ q: prefix }).$promise
}