I am expending a lot of time trying to understand how the $httpBackend and the angular-translate could work together in order to test if the translation functionality still works.
I am in this point, and I really don't know how to solve this problem.
'use strict';
describe('Directive: translate', function () {
beforeEach(function () {
angular.module('myApp', ['pascalprecht.translate']);
});
var element,
$compile,
$rootScope,
$http,
$httpBackend;
beforeEach(inject(function (_$rootScope_, _$compile_, _$httpBackend_, _$http_) {
$compile = _$compile_;
$rootScope = _$rootScope_;
$http = _$http_;
$httpBackend = _$httpBackend_;
}));
afterEach(function() {
$httpBackend.verifyNoOutstandingExpectation();
$httpBackend.verifyNoOutstandingRequest();
});
it('should translate to English', function () {
element = $compile('<p translate>discover_more</p>')($rootScope);
$rootScope.$digest();
$httpBackend.expect('GET', 'langs/en.json').respond(200); // Should I return some data at this point?
$http.get('langs/en.json').then(function () {}); // Should I do something here?
$httpBackend.flush();
expect(element.html()).toBe('Discover more');
});
});
My test of course fails. The thing is that I don't know how to 1) really get the JSON with the data and 2) say the directive "here is your data, do your work".
Edit:
Ok, some light over the issue. I just was looking at the testing of this angular module (https://github.com/angular-translate/angular-translate/tree/master/test/unit/directive) and I could make it work:
'use strict';
describe('Directive: translate', function () {
beforeEach(function () {
angular.module('gajoApp', ['pascalprecht.translate']);
});
var element,
$compile,
$rootScope;
beforeEach(module('pascalprecht.translate', function ($translateProvider) {
$translateProvider
.translations('en', {
'discover_more': 'Discover more'
})
.preferredLanguage('en');
}));
beforeEach(inject(function (_$rootScope_, _$compile_) {
$compile = _$compile_;
$rootScope = _$rootScope_;
}));
it('should translate to English', function () {
element = $compile('<p translate>discover_more</p>')($rootScope);
$rootScope.$digest();
expect(element.html()).toBe('Discover more');
});
});
What I would like, however, is combine this solution with the proper AJAX calls that return the JSON, to test that this is been done too.
You should return from your expected GET request whatever the angular-translate need to actually replace the discover_more in your element.
beforeEach(function () {
$httpBackend.when(
'GET',
'langs/en.json'
).respond({'discover_more': 'Discover more'});
});
Note, that I dont know the excact object that angular-translate expects, so it could differ from my suggestion. Anyway return it when the GET request is perceived.
In addition you're not supposed to make the GET request yourself within your test. If everything is set uo correctly, it should work if you add the expected return to your expected GET request.
Unfortunately it is due to restrictions on angular-translate, but you can use your real JSON locale file by either:
1) Using a plugin to load JSON files combined with $httpBackend to load your locale file when angular-translate requests it.
beforeEach(inject(function (_$httpBackend_) {
$httpBackend = _$httpBackend_;
$httpBackend.whenGET('locale-pt.json').respond(readJSON('langs/en.json'));
$httpBackend.flush();
})));
2) Overriding your app's translations with the $translateProvider.translations() method with a JSON read by a plugin to load JSON files
beforeEach(module(function ($translateProvider) {
$translateProvider.translations('en', readJSON('langs/en.json'));
}));
Note this should be below your beforeEach(module('myApp')); or you will get an $injector error.
Related
I am working on an angular js app with karma/Jasmine testing framework, I need to test a factory that returns a http promise but it always return undefined
here is my factory
angular.module('GithubUsers').factory('Users',['$http','$q',function($http,$q){
return{
getAllUsers:function(){
var defered= $q.defer();
$http({
url:'https://api.github.com/users',
method:'GET'
}).then(function(users){
defered.resolve(users.data);
},function(err){
defered.reject(err);
})
return defered.promise;
}
}
}])
here is my tests
Update thanks to your answers I modified my code to the following but no I got this error
Possibly unhandled rejection: {"status":0,"config":{"method":"GET","transformRequest":[null],"transformResponse":[null],"jsonpCallbackParam":"callback","url":"https://api.github.com/users?since=1","headers":{"Accept":"application/json, text/plain, /"},"cached":false},"statusText":""} thrown
describe('Test Users Factory',function(){
var $controller,
Users,
$rootScope,
$httpBackend,
$q;
beforeEach(module('GithubUsers'));
beforeEach(inject(function(_$controller_,_Users_,_$rootScope_,_$httpBackend_,_$q_){
$controller = _$controller_;
Users = _Users_;
$rootScope= _$rootScope_;
$httpBackend=_$httpBackend_;
}))
it('should get users',function(){
var result;
$httpBackend.whenGET('https://api.github.com/users?since=1').respond(function(){
return {data:[{id:2}],status:200};
})
Users.getAllUsers().then(function(res){
result = res;
});
$httpBackend.flush();
$rootScope.$digest()
expect(result).toBeTruthy();
})
})
Thanks in advance!
I think you need to pass a function that returns a array with 3 items in it, to whenGET().respond().
Maybe, you can try something like this:
beforeEach(angular.mock.inject(function (User, $httpBackend, $http) {
...
this.withOKUsers = function() {
var i1 = new User();
i1.id = 10;
return [200, JSON.stringify([ i1]), {}];
} ...
}));
...
it('should get users',function(){
$httpBackend
.whenGET('https://api.github.com/users')
.respond(this.withOKUsers);
Users.getAllUsers().then(function(res){
result = res;
});
$httpBackend.flush();
expect(result).not.toBeNull();
...
(I prefer to arrange spec outside of it() clause for better readability)
You're missing a $httpBackend.flush(); call after your test method call. It will invoke a success/error or then part and resolve a $q's promise properly. For more tests I would move a $httpBackend.whenGET to each test case separately so I can later verify it per use case but it's just my personal opinion.
I find it a little suspicious that you mix a $controller and a factory in one test. I would suggest to split them, and in controller test just check the calls to service methods and in a facotry test itself do a $httpBackend stuff.
Below I paste your test with my corrections. It works now for me:
describe('Test Users Factory', function () {
var Users,
$rootScope,
$httpBackend,
$q;
beforeEach(module('app.utils'));
beforeEach(inject(function (_Users_, _$rootScope_, _$httpBackend_, _$q_) {
Users = _Users_;
$rootScope = _$rootScope_;
$httpBackend = _$httpBackend_;
}));
afterEach(function () {
$httpBackend.verifyNoOutstandingExpectation();
$httpBackend.verifyNoOutstandingRequest();
});
it('should get users', function () {
var result;
$httpBackend.when('GET', "https://api.github.com/users").respond({ data: [{ id: 2 }], status: 200 });
Users.getAllUsers().then(function (res) {
result = res;
expect(result).toBeTruthy();
});
$httpBackend.flush();
$rootScope.$digest();
});
Important notices:
1)afterEach - check if no pending requests remain after your call
2) your url differ with a parameter ?since=1. But you do not give it as a parameter in your code so i do not understand why you added this parameter.
Maybe consider string concatenation with url and parameter ?
Unit testing a Directive which uses ngModel that has several function including getAll(). Model gets injected perfectly (when I output it, it shows the accessible getters/setters/etc). I pass it to the element. Do a compile and digest.
Getting the error 'TypeError: Cannot read property 'getAll' of undefined' though.
'console.log('vehiclesModel', vehiclesModel.get('vehicles'));'
Outputs the stubbedData!
'use strict';
describe('Directive: selectBox', function () {
beforeEach(module('sytacApp'));
beforeEach(module('syt.templates'));
var scope,
httpBackend,
$rootScope,
$compile,
element,
vehiclesModel,
stubbedData;
beforeEach(function () {
inject(function ($injector) {
$compile = $injector.get('$compile');
});
});
beforeEach(inject(function (_$rootScope_, _$httpBackend_, _vehiclesModel_, _stubbedData_) {
httpBackend = _$httpBackend_;
$rootScope = _$rootScope_;
vehiclesModel = _vehiclesModel_;
stubbedData = _stubbedData_;
vehiclesModel.set('vehicles', {data: stubbedData.container});
console.log('vehiclesModel', vehiclesModel.get('vehicles'));
}));
it('should process model data accordingly', function () {
var element = angular.element('<select-box identifier="type" selectedidentifier="selectedType" model="vehiclesTypesModel" data-ng-model="vehiclesModel"></select-box>');
element = $compile(element)(scope);
scope.$digest();
//......
});
});
Question. Am I overlooking something?
had to put ´vehiclesModel´ on ´scope scope.vehiclesModel´ before ´$compile´
I have an angular app, that on initialisation, make a number of http requests.
I have set up a test, to expect the first request, and the second,
describe("MyController--", function () {
var controller, scope, $httpBackend, myFactory;
var iResponse = {
table: 'red',
price: '1.99'
};
beforeEach(angular.mock.module('myApp'));
beforeEach(inject(function (_$controller_, _$rootScope_, _$httpBackend_, _myFactory_) {
scope = _$rootScope_.$new();
$httpBackend = _$httpBackend_;
$httpBackend.expectGET("/app/shopping/cart/layout.html").respond({});
$httpBackend.expectGET("/app/rest/products").respond(iResponse);
myFactory = _myFactory_;
spyOn(myFactory, 'getData').and.callThrough();
controller = _$controller_('MainController', {$scope: scope});
scope.$apply();
$httpBackend.flush();
}));
it('should verify that the controller exists ', function() {
expect(controller).toBeDefined();
});
With the above, i keep seeing the error:
Error: Unexpected request: GET /app/rest/products
Information: Expected GET /app/shopping/cart/layout.html
Any ideas what i am missing?
Firstly I would preload HTML in karma conf so u don't have to expect HTML
Secondly does your controller make a call to the unexpected url? If so u shud expect the request
All the best
I did this controller
app.controller('controller',['$scope','httpServices',function($scope,httpServices){
$scope.items= undefined;
httpServices.getItems( function(items){
$scope.items= items;
});
}]);
and I wrote this test
describe('controller', function () {
beforeEach(inject(function ($rootScope, $controller) {
scope = $rootScope.$new();
controller = $controller('controller', {
'$scope': scope
});
}));
it('defined', function () {
expect(scope.items).toBeUndefined();
})
});
How I can test the scope.items after to have called the service?
I assume that your service httpServices is making some http requests. Therefore you should use the mock-backend service in order to test your controller.
Something like this, pay attention to the comments that I've made inside the code:
describe('Your specs', function() {
var $scope,
$controller,
$httpBackend;
// Load the services's module
beforeEach(module('yourApp'));
beforeEach(inject(function(_$controller_, $rootScope, _$httpBackend_) {
$scope = $rootScope.$new();
$httpBackend = _$httpBackend_;
$controller = _$controller_;
//THIS LINE IS VERY IMPORTANT, HERE YOU HAVE TO MOCK THE RESPONSE FROM THE BACKEND
$httpBackend.when('GET', 'http://WHATEVER.COM/API/SOMETHING/').respond({});
var createController = function(){
$controller('controller', {$scope: $scope});
}
}));
describe('Your controller', function() {
it('items should be undefined', function() {
createController();
expect(scope.items).toBeUndefined();
});
it('items should exist after getting the response from the server', function () {
//THIS LINE IS ALSO VERY IMPORTANT, IT EMULATES THE RESPONSE FROM THE SERVER
$httpBackend.flush();
expect(scope.items).toBeDefined();
});
});
});
The question title states this is to test a service, but the code of the question looks like an attempt is being made to test the controller. This answer describes how to test the controller.
If you're testing the controller that calls httpServices.getItems, then you need to mock it/stub getItems in order to
Control it on the test
Not assume any behaviour of the real httpServices.getItems. After all, you're testing the controller, and not the service.
A way to do this is in a beforeEach block (called before the controller is created) provide a fake implementation of getItems that just saves the callback passed to it.
var callback;
beforeEach(inject(function(httpServices) {
callback = null;
spyOn(httpServices, 'getItems').and.callFake(function(_callback_) {
callback = _callback_;
});
});
In the test you can then call this callback, passing in some fake data, and test that this has been set properly on the scope.
it('saves the items passed to the callback on the scope', function () {
var testItems = {};
callback(testItems);
expect($scope.items).toBe(testItems);
});
This can be seen working at http://plnkr.co/edit/Z7N6pZjCS9ojs9PZFD04?p=preview
If you do want to test httpServices.getItems itself, then separate tests are the place for that. Assuming getItems calls $http, then you are most likely to need to use $httpBackend to handle mock responses. Most likely, these tests would not instantiate any controller, and I suspect not need to do anything on any scope.
I put the code in a fiddle so it can be easily updated and 'worked with' if needed.
describe('PlayersListCtrl', function() { // Jasmine Test Suite
beforeEach(module('wc2014App'));
var ctrl, scope, $httpBackend;
beforeEach(inject(function($controller, $rootScope) {
scope = $rootScope.$new();
ctrl = $controller('PlayersListCtrl', {
$scope: scope
});
}));
it('should have an empty player array', function() {
expect(scope.players.length).toBe(0);
});
describe('PlayersListCtrl', function() {
var $httpBackend, $rootScope, createController;
beforeEach(inject(function($injector) {
$httpBackend = $injector.get('$httpBackend');
$httpBackend.when('GET', '../app/stubs/players.json').respond(
{userId: 'userX'},
{'A-Token': 'xxx'});
$rootScope = $injector.get('$rootScope');
var $controller = $injector.get('$controller');
createController = function() {
return $controller('PlayersListCtrl', {'$scope' : $rootScope });
};
}));
afterEach(function() {
$httpBackend.verifyNoOutstandingExpectation();
$httpBackend.verifyNoOutstandingRequest();
});
it('should fetch authentication token', function() {
$httpBackend.expectGET('../app/stubs/players.json');
var controller = createController();
$httpBackend.flush();
});
});
});
The rest, cause its quite verbose, is in the fiddle: http://jsfiddle.net/tLte2/
Basically the first test passes, not a hard one, but the second one depends on a JSON stub and gives errors like:
PhantomJS 1.9.7 (Mac OS X) PlayersListCtrl PlayersListCtrl should fetch authentication token FAILED
Error: No pending request to flush !
Cant seem to get a grip on how this $httpBackend stiff works. Is must be possible to just fire it and set the result in the scope of the controller?
--edit
Basically got everything wired up perfectly and can do some simple tests that run just fine, however getting JSON stub data in there seems to be a pain. Workaround can be just defining the array described in the the JSON on the controller scope like: controller.players = ['one','two','three',..... etc ......]
But that doesnt feel right. That $httpBackend stuff shouldn't be that hard to fix right?