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");
});
});
Related
I can find plenty of examples of single http calls from a controller and how to test them,but no examples of multiple testing.
My first test works fine without Product.find(10) in the controller. When I add that line however the first test collapses.
The errors:
Error: Unexpected request: GET 0.0.0.0:3000/api/products
No more request expected
and
Error: Unexpected request: GET 0.0.0.0:3000/api/products
No more request expected
I've tried a number of things: including both in the before each, this gave me an undefined error, i tried using expect instead of when, I tried adding both whens to both tests, and a combination of the above. I'm clearly doing something very wrong but being an angular newbie, it's hard to work out exactly what that might be, especially with the lack of examples.. I am just looking to get my first test to pass with Product.find(10)
Here are my tests:
'use strict';
describe('productsController', function() {
var scope, $httpBackend;
var api_root = '0.0.0.0:3000/api/';
beforeEach(angular.mock.module('sprangularApp'));
beforeEach(angular.mock.inject( function($rootScope, $controller, _$httpBackend_) {
$httpBackend = _$httpBackend_;
//Get mock jsons
jasmine.getJSONFixtures().fixturesPath='base/js/tests/api_mock';
scope = $rootScope.$new();
$controller('productsController', {$scope: scope});
}));
//Start Tests
it('Should be array of all products', function() {
$httpBackend.when('GET', api_root + 'products').respond(
getJSONFixture('products.json')
);
$httpBackend.flush();
expect(scope.products[3].name).toBe('Ruby on Rails Bag');
});
it('Should instantiate a new product object from json data', function() {
$httpBackend.when('GET', api_root + 'products/10').respond(
getJSONFixture('10.json')
);
$httpBackend.flush();
expect(scope.currentProduct.name).toBe('Spree Ringer T-Shirt');
});
});
my controller that I am testing:
// Generated by CoffeeScript 1.6.3
(function() {
var sprangularControllers;
sprangularControllers = angular.module('sprangularControllers', ['sprangularServices']);
sprangularControllers.controller('productsController', [
'$scope', 'Product', function($scope, Product) {
Product.products_with_meta().$promise.then(function(response) {
return $scope.products = response.products;
});
return Product.find(10);
}
]);
}).call(this);
And the factory with the resource requests:
sprangularServices = angular.module('sprangularServices', ['ngResource'])
sprangularServices.factory('Defaults', ->
api_url: "0.0.0.0:3000/api/"
)
sprangularServices.factory('Product', ($resource, Defaults) ->
# $resource(Defaults.api_url + 'products.json')
class Product
constructor: ->
#service = $resource(Defaults.api_url + 'products/:id', {id: '#id'})
this.products_with_meta = ->
service = $resource(Defaults.api_url + 'products')
service.get()
this.find = (id) ->
service = $resource(Defaults.api_url + 'products/:id', {id: id})
service.get()
)
As per michael's suggestion I have edited my test to this, however I am still getting the exact same result:
'use strict';
describe('productsController', function() {
var $rootScope, $httpBackend, createController;
var api_root = '0.0.0.0:3000/api/';
beforeEach(angular.mock.module('sprangularApp'));
beforeEach(inject(function($injector) {
$httpBackend = $injector.get('$httpBackend');
//Get mock jsons
jasmine.getJSONFixtures().fixturesPath='base/js/tests/api_mock';
$rootScope = $injector.get('$rootScope');
var $controller = $injector.get('$controller');
createController = function() {
return $controller('productsController', {'$scope' : $rootScope });
};
}));
afterEach(function() {
$httpBackend.verifyNoOutstandingExpectation();
$httpBackend.verifyNoOutstandingRequest();
});
//Start Tests
it('Should be array of all products', function() {
$httpBackend.when('GET', api_root + 'products').respond(
getJSONFixture('products.json')
);
var controller = createController();
$httpBackend.flush();
expect($rootScope.products[3].name).toBe('Ruby on Rails Bag');
});
it('Should instantiate a new product object from json data', function() {
$httpBackend.when('GET', api_root + 'products/10').respond(
getJSONFixture('10.json')
);
var controller = createController();
$httpBackend.flush();
expect($rootScope.currentProduct.name).toBe('Spree Ringer T-Shirt');
});
});
Structuring my test in this way seemed to solve the issue:
'use strict';
describe('productsController', function() {
var $rootScope, $httpBackend, createController;
var api_root = '0.0.0.0:3000/api/';
beforeEach(angular.mock.module('sprangularApp'));
beforeEach(inject(function($injector) {
$httpBackend = $injector.get('$httpBackend');
//Get mock jsons
jasmine.getJSONFixtures().fixturesPath='base/js/tests/api_mock';
$rootScope = $injector.get('$rootScope');
var $controller = $injector.get('$controller');
createController = function() {
return $controller('productsController', {'$scope' : $rootScope });
};
$httpBackend.when('GET', api_root + 'products').respond(
getJSONFixture('products.json')
);
$httpBackend.when('GET', api_root + 'products/10').respond(
getJSONFixture('10.json')
);
var controller = createController();
$httpBackend.flush();
}));
afterEach(function() {
$httpBackend.verifyNoOutstandingExpectation();
$httpBackend.verifyNoOutstandingRequest();
$httpBackend.resetExpectations();
});
//Start Tests
it('Should be array of all products', function() {
expect($rootScope.products[3].name).toBe('Ruby on Rails Bag');
});
it('Should instantiate a new product object from json data', function() {
expect($rootScope.currentProduct.name).toBe('Spree Ringer T-Shirt');
});
});
I suppose the order of define the response, do the http call, flush and do the test is not right.
define how the http call should respond
$httpBackend.when('GET', api_root + 'products').respond(
getJSONFixture('products.json')
);
do the call from your code
$controller('productsController', {$scope: scope});
flush the httpBackend (e.g. simulate the asynchronous behavior of $http)
$httpBackend.flush();
do the test
expect(scope.products[3].name).toBe('Ruby on Rails Bag');
because your controller did a backend call in his constructor and is instantiated before you define what the response should be, you got the error.
Further information and an exmaple the is very close to your use case: http://docs.angularjs.org/api/ngMock.$httpBackend
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');
});
});
The unittest:
"use strict";
var usersJSON = {};
describe("mainT", function () {
var ctrl, scope, httpBackend, locationMock,
beforeEach(module("testK"));
beforeEach(inject(function ($controller, $rootScope, $httpBackend, $location, $injector) {
scope = $rootScope.$new();
httpBackend = $httpBackend;
locationMock = $location;
var lUrl = "../solr/users/select?indent=true&wt=json",
lRequestHandler = httpBackend.expect("GET", lUrl);
lRequestHandler.respond(200, usersJSON);
ctrl = $controller("mainT.controller.users", { $scope: scope, $location: locationMock});
httpBackend.flush();
expect(scope.users).toBeDefined();
}));
afterEach(function () {
httpBackend.verifyNoOutstandingRequest();
httpBackend.verifyNoOutstandingExpectation();
});
describe("method test", function () {
it('should test', function () {
expect(true).toBeFalsy();
});
});
});
controller I'm testing (working):
Asynchrone function in init who's giving me trouble (uses ../solr/users/select?indent=true&wt=json):
$scope.search = function () {
var lStart = 0,
lLimit = privates.page * privates.limit;
Search.get({
collection: "users",
start: lStart,
rows: lLimit)
}, function(records){
$scope.users= records.response.docs;
});
};
What I think happens:
1. inform backend what request he will receive
2. inform backend to response on that request with empty JSON
3. create a controller (Search.get get's executed)
4. inform backend to receive all requests and answer them (flush)
Yet I always get the following error:
Error: Unexpected request: GET : ../solr/users/select?indent=true&wt=json
Am I not handling the asynchrone search function well? how should this be done?
That's not really a "unit" test, it's more of a behavioral test.
This should really be a few tests:
Test your service Search.get to make sure it's calling the proper URL and returning the result.
Test your controller method to make sure it's calling Search.get
Test your controller method to make sure it's putting the result in the proper spot.
The code you've posted is a little incomplete, but here are two unit tests that should cover you:
This is something I've blogged about extensively, and the entries go into more detail:
Unit Testing Angular Controllers
Unit Testing Angular Services
Here's an example of what I'm talking about:
describe('Search', function () {
var Search,
$httpBackend;
beforeEach(function () {
module('myModule');
inject(function (_Search_, _$httpBackend_) {
Search = _Search_;
$httpBackend = _$httpBackend_;
});
});
describe('get()', function () {
var mockResult;
it('should call the proper url and return a promise with the data.', function () {
mockResult = { foo: 'bar' };
$httpBackend.expectGET('http://sample.com/url/here').respond(mockResult);
var resultOut,
handler = jasmine.createSpy('result handler');
Search.get({ arg1: 'wee' }).then(handler);
$httpBackend.flush();
expect(handler).toHaveBeenCalledWith(mockResult);
$httpBackend.verifyNoOutstandingRequest();
$httpBackend.verifyNoOutstandingExpectation();
});
});
});
describe('myCtrl', function () {
var myCtrl,
$scope,
Search;
beforeEach(function () {
module('myModule');
inject(function ($rootScope, $controller, _Search_) {
$scope = $rootScope.$new();
Search = _Search;
myCtrl = $controller('MyCtrl', {
$scope: scope
});
});
});
describe('$scope.foo()', function () {
var mockResult = { foo: 'bar' };
beforeEach(function () {
//set up a spy.
spyOn(Search, 'get').andReturn({
then: function (fn) {
// this is going to execute your handler and do whatever
// you've programmed it to do.. like $scope.results = data; or
// something.
fn(mockResult);
}
});
$scope.foo();
});
it('should call Search.get().', function () {
expect(Search.get).toHaveBeenCalled();
});
it('should set $scope.results with the results returned from Search.get', function () {
expect(Search.results).toBe(mockResult);
});
});
});
In a BeforeEach you should use httpBackend.when instead of httpBackend.expect. I don't think you should have an assertion (expect) in your BeforeEach, so that should be moved to a separate it() block. I also don't see where lRequestHandler is defined. The 200 status is sent by default so that is not needed. Your httpBackend line should look like this:
httpBackend.when("GET", "/solr/users/select?indent=true&wt=json").respond({});
Your test should then be:
describe("method test", function () {
it('scope.user should be defined: ', function () {
expect(scope.user).toEqual({});
});
});
Your lUrl in the unit test, shouldn't be a relative path, i.e., instead of "../solr/users/select?indent=true&wt=json" it should be an absolute "/solr/users/select?indent=true&wt=json". So if your application is running at "http://localhost/a/b/index.html", lUrl should be "/a/solr/...".
Note that you can also use regular expressions in $httpBackend.expectGET(), that could be helpful here in case you are not entirely sure how the absolute path will look like later on.
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.
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() {
});
});