Angular Jasmine unit test with AJAX data - javascript

I have a service, DataContext which returns me a set of data that I want to use in my controller. This data is used by an ng-grid directive. The options for the grid are supplied by GridOptionsService.
All of this works fine, but I'm trying to write a unit test to check and see if everything's working.
describe('Grid display test', function() {
var $scope, elm, oCtrl;
beforeEach(module('ngGrid'));
beforeEach(inject(function ($rootScope, $compile, $controller,DataContext,GridOptionsService) {
$scope = $rootScope.$new();
$scope.gridOptions = GridOptionsService.getGridOptions('documents');
$scope = $rootScope;
elm = angular.element('<div ng-grid="gridOptions"></div>');
oCtrl = $controller('Repository',{$scope: $scope});
$compile(elm)($scope)
DataContext.getDocuments().then(function(data){
$scope.myData = data;
console.log('here are the grid options: ')
console.log($scope.gridOptions);
})
}));
it('should display rows',function(done){
inject(function($rootScope, $compile, $controller, DataContext,GridOptionsService){
$rootScope.$apply(function(){
DataContext.getDocuments().then(function(data){
expect(data.length).toBe(1000);
done();
})
})
})
})
});
DataContext.getDocuments returns a promise and I use that to set the myData variable of the controller. This data is the data for the grid.
$scope.gridOptions = GridOptionsService.getGridOptions('documents');
gridOptions is simply a JS object returned from a service. When I run the test I get the error: Error: [$injector:unpr] Unknown provider: DataContextProvider <- DataContext
All of the scripts that should be included in the spec runner are, and the code definitely works, but I just don't know how to test it.
How can I test AJAX code that changes the appearance of my DOM with Jasmine?

Try mocking out your custom dependencies for your Jasmine test. The [$injector:unpr] results from the $injector being unable to resolve a required dependency. You can use $provide to register components with the injector. Try to add something like...
beforeEach(function () {
mockDependency = {
getDataSet: function () {
return 'mockDataSet';
}
};
module(function ($provide) {
$provide.value('DataContext', mockDependency);
});
});
Here's the documentation for $provide...https://docs.angularjs.org/api/auto/object/$provide
Hope that helps

Related

jasmine mocking angular $http - Error: describe does not expect a done parameter

I have an angular factory like this:
.factory('widgetFactory', ['$http', function($http){
function getWidgets(){
return $http.get('http://example.com/api/widgets/')
.then(function(response){
return response;
});
}
return {
getWidgets:getWidgets
};
}])
And I have the following jasmine test:
describe('widgetFactory', function ($q) {
var mockHttp,
fakeResponse = 'response'
beforeEach(function() {
mockHttp = {
get: jasmine.createSpy('get spy').and.callFake(function () {
var deferred = $q.defer();
deferred.resolve(fakeResponse);
return deferred.promise;
})
};
module(function ($provide) {
$provide.value('$http', mockHttp);
});
});
it('should call api when getWidgets is called', inject(function (widgetFactory) {
var result;
widgetFactory.getWidgets().then(function(response){
result = response;
});
expect(mockHttp.post).toHaveBeenCalledWith('http://example.com/api/widgets/');
expect(result).toBe(fakeResponse);
}));
});
But I get the following error: describe does not expect a done parameter
I think it may be to do with how I'm using $q in my test (other examples I've seen have inject(function($q){ ... inside the beforeEach, but I can't due to my use of module inside beforeEach as this then gives me the following error: Injector already created, can not register a module!)
Any ideas?
You can't inject in describe method. Here I've reworked your version to use ngMock and get rid of mockHttp. I hope it explains a little bit how ngMock works
describe('widgetFactory', function () {
var mockHttp,
fakeResponse = 'response',
getWidgetsDefer,
getWidgetsPromise,
$httpBackend,
widgetFactory,
$q;
beforeEach(module('plunker'));
beforeEach(inject(function($injector) {
$httpBackend = $injector.get('$httpBackend');
$q = $injector.get('$q');
widgetFactory = $injector.get('widgetFactory');
}));
beforeEach(function() {
getWidgetsDefer = $q.defer();
$httpBackend.when('GET', 'http://example.com/api/widgets/')
.respond(getWidgetsDefer);
getWidgetsPromise = widgetFactory.getWidgets();
});
afterEach(function() {
$httpBackend.verifyNoOutstandingExpectation();
$httpBackend.verifyNoOutstandingRequest();
});
it('should call api when getWidgets is called', inject(function (widgetFactory) {
expect($httpBackend.flush).not.toThrow();
}));
});
You can find plunker here
'done' function is the argument that is expected in Jasmine blocks, but not in describe, that's the meaning of the error. Angular services can't be injected without inject wrapper because Jasmine is unaware of them, and the problem can't be solved just by ignoring this fact.
angular.mock.module is able to mock services with object argument, there's no need to re-invent the wheel.
Unfortunately, mocked services are meant to be self-contained, and it won't solve the problem with $q, so it has to be injected in addition after module:
var $q;
beforeEach(function() {
module({
$http: { ... }
});
inject(function (_$q_) {
$q = _$q_;
});
})
Fortunately, ngMock provides $httpBackend mock, so mocking $http is pointless. In fact, real request shouldn't (and can't) be performed with ngMock. The spec for widget service becomes as slim as that:
widgetFactory.getWidgets();
$httpBackend.expect('GET', 'http://example.com/api/widgets/').respond(fakeResponse);
expect($httpBackend.flush).not.toThrow();
Notice that it doesn't matter if the request was mocked before or after $http.get call, the requests are solved when $httpBackend.flush() is called. And fakeResponse === fakeResponse check can be safely skipped as well.

Angular Jasmine UI router inject resolve value into test

In my Angular app, UI router resolves a promise into the controller. When trying to test this controller, Karma is complaining about an unknown provider. How do I inject a fake object into the test to represent this resolve object.
My app's code looks something like:
angular.module('myapp')
.config(function($stateProvider, $urlRouterProvider) {
$stateProvider
.state('tab.name', {
...
resolve: {
allTemplates: function(Templates) {
return Templates.all().then(function(templates) {
return templates;
});
}
}
})
})
.controller('QueriesCtrl', function(allTemplates, UserQuery) {
var vm = this;
vm.queries = allTemplates;
vm.goToUrl = function(index, data) {
var processedUrl = UserQuery.process(data, vm.queryTyped[index]);
UserQuery.goToUrl(processedUrl);
};
});
When trying to run tests I get the error
Unknown provider: allTemplatesProvider <- allTemplates <- QueriesCtrl
I've tried creating a spy and injecting it, but this does not work. Here's my test at the moment:
describe('Unit: queriesCtrl', function() {
var controller,
scope,
UserQuery;
beforeEach(function() {
module('myapp');
inject(function($injector) {
UserQuery = $injector.get('UserQuery');
allTemplates = jasmine.createSpyObj('allTemplates', [{a:1}, {a:2}, {b:3}]);
});
});
describe('goToUrl', function() {
beforeEach(inject(function ($rootScope, $controller) {
scope = $rootScope.$new();
controller = $controller('QueriesCtrl as ctrl', {
'$scope': scope
});
}));
it('should call UserQuery.process()', function() {
spyOn(UserQuery, 'process');
scope.ctrl.goToUrl();
expect(UserQuery.process).toHaveBeenCalled();
});
});
});
Since there is no route involved in unit test you would have to inject the allTemplates as a normal object with $controller function. Can you try:
controller = $controller('QueriesCtrl as ctrl', {
'$scope': scope,
'allTemplates':allTemplates
});
Else you can use the $provide API to create a dummy service.
module(function ($provide) {
$provide.value("allTemplates", {[{a:1}, {a:2}, {b:3}]});
Do it first thing in your beforEach block.

Unit testing AngularJS Controller whilst following best practice

We are building an AngularJS app following some of the best practice guidelines which are outlined here.
Am specifically interested in testing a very simple controller to get up and running with karma.
The controller code is:
angular.module('ttn').controller('Login', Login);
function Login(){
var login = this;
login.title = 'foo bar content here etc';
}
And the spec code is:
describe('Controller: Login', function () {
beforeEach(module('ttn'));
var scope, controller;
beforeEach(inject(function ($controller, $rootScope) {
scope = $rootScope.$new();
controller = $controller('Login', {
$scope: scope
});
scope.$digest();
}));
it('should define a title', function () {
expect(scope.title).toBeDefined();
});
});
This fails with expecting undefined to be defined.
If I change the controller to:
angular.module('ttn').controller('Login', Login);
function Login($scope){
$scope.title = 'foo bar whatsit jibber';
}
The test then passes as expected. I am not sure how to reference the controller written in the manner outlined on the above link to get the test to pass.
Since your controller doesn't use $scope, you shouldn't be injecting it and using it in your tests. Instead you should be checking for title on your controller:
describe('Controller: Login', function () {
beforeEach(module('ttn'));
var controller;
beforeEach(inject(function ($controller) {
controller = $controller('Login', {});
}));
it('should define a title', function () {
expect(controller.title).toBeDefined();
});
});
Plunkr

Unit testing angular/Ionic project

I have a very simple controller that looks like this.
timeInOut.controller('timeInOutController', function($scope, $filter, $ionicScrollDelegate){
...
});
Whenever I try to create a unit test for it like so...
(function() {
'use strict';
var scope, controller, filter;
describe('timeInOutController', function () {
beforeEach(module('common.directives.kmDateToday'));
beforeEach(inject(function ($rootScope, $controller, $filter) {
scope = $rootScope.$new();
filter = $filter;
controller = $controller('timeInOutController', {
$scope: scope
});
}));
describe('#date setting', function(){
...
});
});
})();
I get the error:
[$injector:unpr] Unknown provider: $ionicScrollDelegateProvider <- $ionicScrollDelegate
Obviously in my example here I'm not trying to inject the $ionicScrollDelegate into the test, that's just because I've tried it any number of ways with no success and don't know which failed attempt to include.
Also in my karma.conf.js file I am including the ionic.bundle.js and angular-mocks.js libraries/files.
I can successfully unit test anything that doesn't use anything $ionic in it, so I know my testing framework is set up correctly, the issue is injecting anything ionic related.
You need to pass in all the parameters if you're going to instantiate your controller via angular. By adding the parameters you are telling angular that any time you create one of these controllers I need these things too because I am dependent upon them.
So my suggestion is to mock up some representation of these dependencies and inject them in when you are creating the controller. They do not have to be (and should not be) the actual services for your unit tests. Jasmine gives you the ability to create spy objects that you can inject so you can verify the the behavior of this unit.
(function() {
'use strict';
var scope, controller, filter, ionicScrollDelegate;
describe('timeInOutController', function () {
beforeEach(module('common.directives.kmDateToday'));
beforeEach(inject(function ($rootScope, $controller, $filter) {
scope = $rootScope.$new();
filter = $filter;
// func1 and func2 are functions that will be created as spies on ionicScrollDelegate
ionicScrollDelegate = jasmine.createSpyObj('ionicScrollDelegate', ['func1', 'func2']
controller = $controller('timeInOutController', {
$scope: scope,
$filter: filter,
$ionicScrollDelegate: ionicScrollDelegate
});
}));
describe('#date setting', function(){
...
});
});
})();
You can find more about spies via jasmine's documentation
You need to create mock objects for all dependencies your controller is using.
Take this controller as an example:
angular.module('app.module', [])
.controller('Ctrl', function($scope, $ionicLoading) {
$ionicLoading.show();
});
Here you are using the $ionicLoading service, so if you want to test this controller, you have to mock that object specifying the methods you're using in the controller:
describe('Test', function() {
// Mocks
var $scope, ionicLoadingMock;
var ctrl;
beforeEach(module('app.module'));
beforeEach(function() {
// Create $ionicLoading mock with `show` method
ionicLoadingMock = jasmine.createSpyObj('ionicLoading', ['show']);
inject(function($rootScope, $controller) {
$scope = $rootScope.$new();
ctrl = $controller('Ctrl', {
$scope: $scope,
$ionicLoading: ionicLoadingMock
});
});
});
// Your test goes here
it('should init controller for testing', function() {
expect(true).toBe(true);
});
});

angularjs karma test typeerror $route

I am trying to test a controller for angularjs using karma, the controller inject a $route to get the current path but when I try to run karma test on it I get.
TypeError: 'undefined' is not an object( evaluating '$route.current')
Here is my controller:
angular.module('myApp').controller('EditController',['$scope', '$http', '$route', function($scope,
$http,$route){
var myId = $route.current.params.myId;
$scope.var1 = 'var1';
console.log(myId);
}]);
Here is my Karma file:
'use strict';
describe('Controller: EditController', function(){
beforeEach(module('myApp'));
var EditCtrl,scope,route;
beforeEach(inject(function($controller,$rootScope,$route,$http){
scope=$rootScope.$new();
EditCtrl = $controller('EditCtrl',{
$scope:scope,
$route:route
});
}));
it('should have var1 equal to "var1"',function(){
expect(scope.var1).toEqual('var1');
});
});
Your beforeEach hook isn't injecting the $route service. Change it to this.
beforeEach(inject(function($controller,$rootScope,$route,$http){
scope=$rootScope.$new();
route = $route;
EditCtrl = $controller('EditCtrl',{
$scope:scope,
$route:route
});
}));
You may also want to mock the $route.current object in case it isn't instantiated properly, since there's no routing going on in your test. In which case you could just add
$route.current = { params: { myId: 'test' } };
in the hook as well.

Categories

Resources