angularjs controller test failed - unit-testing

I am struggle up to write a simple angularjs controller testcase by angular-mock and jasmine.
I am using angularjs v1.0.8 and jasmine 1.3.0.
My code is like this:
1. demoController.js
'use strict';
var app = angular.module('MyApp', []);
app.controller('MainCtrl', function($scope) {
$scope.data = 'hello';
});
2. controllerTest.js
'use strict';
describe('MainCtrl', function () {
var scope = null;
beforeEach(angular.module('MyApp'));
beforeEach(inject(function($rootScope, $controller){
scope = $rootScope.$new();
$controller('MainCtrl', {$scope: scope});
}));
it('should have variable text = "Hello World!"', function(){
expect(scope.data).toBe('hello');
});
});
As you can find from my SpecRunner.htm file I have the following files in path.
angular-1.0.8.min.js
angular-mocks.js
angular-scenario.js
angular-resource.js
jasmine.js
jasmine-html.js
demoControllers.js
controllerTest.js"
Now it I execute the html file, I am receiving the following error.
**TypeError: Object #<Object> has no method 'apply'**
**ReferenceError: inject is not defined**
at null.<anonymous>
So my question is, am I doing anything wrong or missed any configuration?

Try this code
beforeEach(module('MyApp'));
describe('MainCtrl', function () {
var scope = null;
beforeEach(inject(function($rootScope, $controller){
scope = $rootScope.$new();
$controller('MainCtrl', {$scope: scope});
}));
it('should have variable text = "Hello World!"', function(){
expect(scope.data).toBe('hello');
});
});

Related

Testing angular factories with jasmine

I have a problem with jasmine and angular. I have to test my factories.
I have made simplidfied project on plunker.
Here is demo app:
http://plnkr.co/edit/Agq4xz9NmYeEDWoJguxt
Here is demo test:
http://plnkr.co/edit/ALVKdXO00IEDaKIjMY6u
The first problem is how to make spec runner working without any specification to test.
When you run demo test plunker you will see error:
TypeError: myDataRaw is undefined in http: //run.plnkr.co/Kqz4tGMsdrxoFNKO/app.js (line 39)
Somebody can help?
Thanks
The problem is
In your production code
var dataLoadedPromise = $http.get('myData.xml', {transformResponse: transformResponse});
you expect the return from the server is XML data not json, so when you mock the HTTP, you should use XML instead of Json.
Here is the working code:
describe("controller: MyController", function () {
beforeEach(function () {
module("myApp");
});
var dataFactory;
beforeEach(inject(function ($injector, $controller, $rootScope, $location, $httpBackend) {
this.$location = $location;
this.$httpBackend = $httpBackend;
this.scope = $rootScope.$new();
dataFactory = $injector.get('dataFactory');
this.$httpBackend.expectGET('myData.xml').respond(
"<?xml version=\"1.0\" encoding=\"UTF-8\" ?><myData><persons><person><firstName>Pera</firstName><lastName>Peric</lastName><email>perica#gmail.com</email><score>22</score></person></persons></myData>");
$controller('MyController', {
$scope: this.scope,
$location: $location,
myData: dataFactory
});
}));
afterEach(function () {
this.$httpBackend.verifyNoOutstandingRequest();
this.$httpBackend.verifyNoOutstandingExpectation();
});
describe("successfully parsed persons", function () {
it("should have 2 persons", function () {
dataFactory.getData();
this.$httpBackend.flush();
});
});
});
Updated Demo

Unit testing AngularJS controller with $httpBackend

For the life of me I can't get $httpBackend to work on a controller that does an $http get request. I've tried for hours now =)
I've reduced this to the simplest form I can below. The test passes if I
comment out the $http.get() request in the controller
comment out the "httpMock.flush()" in the test
and change "pig" and "dog" to match
That is, it's a valid, working test and app.
If I put it back in, I get the error shown at the bottom.
app/js/app.js
// Declare a module which depends on filters and services.
var myApp = angular
.module('myApp', ['ngRoute', 'myApp.filters', 'myApp.services',
'myApp.directives'])
.config(['$routeProvider' , function($routeProvider) {
$routeProvider
.when("/dashboard", {
templateUrl: "partials/dashboard.html",
controller: cDashboard
})
.otherwise({redirectTo: "/dashboard"});
}]);
// Pre-define our main namespace modules.
angular.module('myApp.directives' , []);
angular.module('myApp.filters' , []);
angular.module('myApp.services' , []);
angular.module('myApp.controllers', []);
app/js/controller.js
function cDashboard ($scope, $http) {
$scope.data = "dog";
// Fetch the actual data.
$http.get("/data")
.success(function (data) { $scope.data = data })
.error(function () {});
}
cDashboard.$inject = [ '$scope', '$http' ];
test/unit/controllerSpec.js
describe('cDashboard', function(){
var scope, ctrl, httpMock;
beforeEach(inject(function ($rootScope, $controller, $http, $httpBackend) {
scope = $rootScope.$new();
ctrl = $controller('cDashboard', {$scope: scope});
httpMock = $httpBackend;
httpMock.when("GET", "/data").respond("pig");
}));
it("should get 'pig' from '/data'", function () {
httpMock.expectGET("/data").respond("pig");
expect(scope.data).toBe("pig");
});
});
And this is the error I get in the shell:
INFO [watcher]: Changed file "/home/myApp/test/unit/controller/cDashboard.js".
Chrome 26.0 (Linux) cDashboard should get 'pig' from '/data' FAILED
Error: No pending request to flush !
at Error (<anonymous>)
at Function.$httpBackend.flush (/home/myApp/test/lib/angular/angular-mocks.js:1171:34)
at null.<anonymous> (/home/myApp/test/unit/controller/cDashboard.js:15:18)
Chrome 26.0 (Linux): Executed 1 of 1 (1 FAILED) (0.326 secs / 0.008 secs)
There are a couple problems in your test code:
The controller is created before the httpMock is configured to respond with pig. The expectGet call should happen before instantiating the controller.
The httpMock needs to flush the request
The httMock.when is unnecessary so long as you have the expectGet
Working example: http://plnkr.co/edit/lUkDMrsy1KJNai3ndtng?p=preview
describe('cDashboard', function(){
var scope, controllerService, httpMock;
beforeEach(inject(function ($rootScope, $controller, $httpBackend) {
scope = $rootScope.$new();
controllerService = $controller;
httpMock = $httpBackend;
}));
it("should get 'pig' from '/data'", function () {
httpMock.expectGET("/data").respond("pig");
ctrl = controllerService('cDashboard', {$scope: scope});
httpMock.flush();
expect(scope.data).toBe("pig");
});
});

Unit-testing a controller that uses $http

I have a simple controller and the first thing I need it to do is assign a value to scope.
function TestCtrl($scope, $http) {
$scope.listForms = 'some list';
}
The following test for the controller works as expected:
describe('Testing a controller', function() {
var ctrl, scope, httpMock;
beforeEach(inject(function($injector) {
scope = $injector.get('$rootScope').$new();
ctrl = $injector.get('$controller');
ctrl(TestCtrl, { $scope: scope });
}));
it("assigns to scope", function() {
expect(scope.listForms).toMatch("some list");
});
});
But when I change the function to get the list from my API
function TestCtrl($scope, $http) {
$http.get('/api/listForms').success(function(list) {
$scope.aListOfForms = 'some list';
});
}
and the test changes to
describe('Testing a controller', function() {
var ctrl, scope, httpMock;
beforeEach(inject(function($injector) {
httpMock = $injector.get('$httpBackend');
scope = $injector.get('$rootScope').$new();
httpMock.when('GET', '/tactical/api/listOrderForms').respond("an order form");
ctrl = $injector.get('$controller');
ctrl(TestCtrl, {
$scope: scope,
$http: httpMock
});
}));
it("gets the list from the api and assigns it to scope", function() {
httpMock.expectGET('tactical/api/listOrderForms');
expect(scope.orderFormList).toMatch("an order form");
httpMock.flush();
});
});
I get the following errors:
TypeError: 'undefined' is not a function
Expected undefined to match 'an order form'.
Error: No pending request to flush !
Does anyone know what I am doing wrong? Thanks in advance.
$http uses $httpBackend to talk to external resources. You have mocked $httpBackend, but the controller still needs to talk to it trough $https interface.
This should do it:
describe('Testing a controller', function() {
var ctrl, scope, httpMock;
beforeEach(inject(function($controller, $rootScope, $httpBackend) {
httpMock = $httpBackend;
scope = $rootScope.$new();
httpMock.when('GET', '/tactical/api/listOrderForms').respond("an order form");
ctrl = $controller;
ctrl(TestCtrl, {
$scope: scope
});
}));
it("gets the list from the api and assigns it to scope", function() {
httpMock.expectGET('tactical/api/listOrderForms');
httpMock.flush();
expect(scope.orderFormList).toMatch("an order form");
});
});
you can't replace $http service as $httpBackend service for your controller manually.
Change
ctrl(TestCtrl, {
$scope: scope,
$http: httpMock
});
to
ctrl(TestCtrl, {
$scope: scope
});
It should work.
You need to call httpMock.flush() before the expect(). The flush call simulates the response returning from the "back end," calling the success function that was bound to the http request.

in angular js while testing the controller got Unknown provider

I have the following controller:
angular.module('samples.controllers',[])
.controller('MainCtrl', ['$scope', 'Samples', function($scope, Samples){
//Controller code
}
Which dependent on the following service:
angular.module('samples.services', []).
factory('Samples', function($http){
// Service code
}
Tried to test the controller using the following code:
describe('Main Controller', function() {
var service, controller, $httpBackend;
beforeEach(module('samples.controllers'));
beforeEach(module('samples.services'));
beforeEach(inject(function(MainCtrl, Samples, _$httpBackend_) {
}));
it('Should fight evil', function() {
});
});
But got the following error:
Error: Unknown provider: MainCtrlProvider <- MainCtrl.
P.s Tried the following post, didn't seem to help
The correct way to test controllers is to use $controller as such:
ctrl = $controller('MainCtrl', {$scope: scope, Samples: service});
Detailed example:
describe('Main Controller', function() {
var ctrl, scope, service;
beforeEach(module('samples'));
beforeEach(inject(function($controller, $rootScope, Samples) {
scope = $rootScope.$new();
service = Samples;
//Create the controller with the new scope
ctrl = $controller('MainCtrl', {
$scope: scope,
Samples: service
});
}));
it('Should call get samples on initialization', function() {
});
});

How to unit test angularjs controller with $location service

I am trying to create a simple unit test that tests my show function.
I get the following error:
TypeError: Object #<Object> has no method 'show'
It seems like $rootScope isn't the scope of the controller?
Here's my controller:
function OpponentsCtrl($scope, $location) {
$scope.show = function(url) {
$location.path(url);
}
}
OpponentsCtrl.$inject = ['$scope', '$location'];
Here's my controller unit test:
describe('OpponentsCtrl', function() {
beforeEach(module(function($provide) {
$provide.factory('OpponentsCtrl', function($location){
// whatever it does...
});
}));
it('should change location when setting it via show function', inject(function($location, $rootScope, OpponentsCtrl) {
$location.path('/new/path');
$rootScope.$apply();
expect($location.path()).toBe('/new/path');
$rootScope.show('/test');
expect($location.path()).toBe('/test');
}));
});
This is how my test ended up working.
describe('OpponentsCtrl', function() {
var scope, rootScope, ctrl, location;
beforeEach(inject(function($location, $rootScope, $controller) {
location = $location;
rootScope = $rootScope;
scope = $rootScope.$new();
ctrl = $controller(OpponentsCtrl, {$scope: scope});
}));
it('should change location when setting it via show function', function() {
location.path('/new/path');
rootScope.$apply();
expect(location.path()).toBe('/new/path');
// test whatever the service should do...
scope.show('/test');
expect(location.path()).toBe('/test');
});
});
Why don't you simply use a spyOn function?
describe('OpponentsCtrl', function() {
var location;
beforeEach(module(function($provide) {
$provide.factory('OpponentsCtrl', function($location){
location = $location;
});
}));
it('should change location when setting it via show function', inject(function() {
spyOn(location, 'path');
expect(location.path).toHaveBeenCalledWith('/new/path');
}));
});
Hope this helps!
I prefer to mock location and services as then it's a unit (not integration) test:
'use strict';
describe('flightController', function () {
var scope;
var searchService;
var location;
beforeEach(module('app'));
beforeEach(inject(function ($controller, $rootScope) {
scope = $rootScope.$new();
mockSearchService();
mockLocation();
createController($controller);
}));
it('changes location to month page', function () {
searchService.flightToUrl.and.returnValue('Spain/Ukraine/December/1');
scope.showMonth();
expect(location.url).toHaveBeenCalledWith('search/month/Spain/Ukraine/December/1');
});
function mockSearchService() {
searchService = jasmine.createSpyObj('searchService', ['flightToUrl']);
}
function mockLocation() {
location = jasmine.createSpyObj('location', ['url']);
}
function createController($controller) {
$controller('flightController', {
$scope: scope,
searchService: searchService,
$location: location
});
}
});
Cheers