I am trying to test a service which is using $http
var APIClient = function($http) {
this.send = function(data) {
$http({
method: data.method,
url: data.url,
headers: data.headers,
data: data.data
}).success(function(response, status) {
data.success(response, status);
}).error(function(response, status) {
data.error(response, status);
});
}
}
angular.module('api.client', []).factory('APIClient', ['$http'
function($http) {
var client = new APIClient($http);
return {
send: function(data) {
return client.send(data);
},
}
}
]);
And the test
describe('send', function() {
var apiClient, $httpBackend;
beforeEach(module('compare'));
beforeEach(inject(function($injector) {
$httpBackend = $injector.get('$httpBackend');
apiClient = $injector.get('APIClient');
}));
it('Should check if send() exists', function() {
expect(apiClient.send).toBeDefined();
});
it('Should send GET request', function(done) {
var url = '/';
$httpBackend.expect('GET', url).respond({});
apiClient.send({
url: url,
success: function(data, status) {
console.log(status);
done();
},
error: function(data, status) {
console.log(status);
done();
}
});
$httpBackend.flush();
});
});
But I always have this error
PhantomJS 1.9.8 (Mac OS X) send Should send GET request FAILED
Error: Unexpected request: GET templates/test.html
Expected GET /
The expected url is always the last state in my app.js
In this case
// Ionic Starter App
// angular.module is a global place for creating, registering and retrieving Angular modules
// 'starter' is the name of this angular module example (also set in a <body> attribute in index.html)
// the 2nd parameter is an array of 'requires'
// 'starter.services' is found in services.js
// 'starter.controllers' is found in controllers.js
angular.module('compare',
[
'ionic',
'manager.user',
'api.client',
'api.user',
'apipare',
'user.controllers',
'test.controllers'
]
)
.run(function ($ionicPlatform) {
$ionicPlatform.ready(function () {
// Hide the accessory bar by default (remove this to show the accessory bar above the keyboard
// for form inputs)
if (window.cordova && window.cordova.plugins && window.cordova.plugins.Keyboard) {
cordova.plugins.Keyboard.hideKeyboardAccessoryBar(true);
}
if (window.StatusBar) {
// org.apache.cordova.statusbar required
StatusBar.styleLightContent();
}
});
})
.config(function ($stateProvider, $urlRouterProvider) {
// Ionic uses AngularUI Router which uses the concept of states
// Learn more here:
// Set up the various states which the app can be in.
// Each state's controller can be found in controllers.js
$stateProvider
// setup an abstract state for the tabs directive
.state('tab', {
url: "/tab",
abstract: true,
templateUrl: "templates/tabs.html"
})
// Each tab has its own nav history stack:
.state('tab.dash', {
url: '/dash',
views: {
'tab-dash': {
templateUrl: 'templates/tab-dash.html',
controller: 'DashCtrl'
}
}
})
.state('subscription', {
url: '/subscription',
templateUrl: 'templates/subscription.html',
controller: 'SubscriptionCtrl'
})
.state('login', {
url: '/login',
templateUrl: 'templates/login.html',
controller: 'LoginCtrl'
})
.state('test-compare', {
url: '/test/compare',
templateUrl: 'templates/test.html',
controller: 'TestCompareCtrl'
})
// if none of the above states are matched, use this as the fallback
$urlRouterProvider.otherwise('/login');
});
I don't understand why the url is changing I am giving /
and it test templates/test.html
which is always the last state template
I am trying to test a service which is using $http
var APIClient = function($http) {
this.send = function(data) {
$http({
method: data.method,
url: data.url,
headers: data.headers,
data: data.data
}).success(function(response, status) {
data.success(response, status);
}).error(function(response, status) {
data.error(response, status);
});
}
}
angular.module('api.client', []).factory('APIClient', ['$http'
function($http) {
var client = new APIClient($http);
return {
send: function(data) {
return client.send(data);
},
}
}
]);
And the test
describe('send', function() {
var apiClient, $httpBackend;
beforeEach(module('compare'));
beforeEach(inject(function($injector) {
$httpBackend = $injector.get('$httpBackend');
apiClient = $injector.get('APIClient');
}));
it('Should check if send() exists', function() {
expect(apiClient.send).toBeDefined();
});
it('Should send GET request', function(done) {
var url = '/';
$httpBackend.expect('GET', url).respond({});
apiClient.send({
url: url,
success: function(data, status) {
console.log(status);
done();
},
error: function(data, status) {
console.log(status);
done();
}
});
$httpBackend.flush();
});
});
But I always have this error
PhantomJS 1.9.8 (Mac OS X) send Should send GET request FAILED
Error: Unexpected request: GET templates/test.html
Expected GET /
The expected url is always the last state in my app.js
In this case
// Ionic Starter App
// angular.module is a global place for creating, registering and retrieving Angular modules
// 'starter' is the name of this angular module example (also set in a <body> attribute in index.html)
// the 2nd parameter is an array of 'requires'
// 'starter.services' is found in services.js
// 'starter.controllers' is found in controllers.js
angular.module('compare',
[
'ionic',
'manager.user',
'api.client',
'api.user',
'api.compare',
'user.controllers',
'test.controllers'
]
)
.run(function ($ionicPlatform) {
$ionicPlatform.ready(function () {
// Hide the accessory bar by default (remove this to show the accessory bar above the keyboard
// for form inputs)
if (window.cordova && window.cordova.plugins && window.cordova.plugins.Keyboard) {
cordova.plugins.Keyboard.hideKeyboardAccessoryBar(true);
}
if (window.StatusBar) {
// org.apache.cordova.statusbar required
StatusBar.styleLightContent();
}
});
})
.config(function ($stateProvider, $urlRouterProvider) {
// Ionic uses AngularUI Router which uses the concept of states
// Learn more here: https://github.com/angular-ui/ui-router
// Set up the various states which the app can be in.
// Each state's controller can be found in controllers.js
$stateProvider
// setup an abstract state for the tabs directive
.state('tab', {
url: "/tab",
abstract: true,
templateUrl: "templates/tabs.html"
})
// Each tab has its own nav history stack:
.state('tab.dash', {
url: '/dash',
views: {
'tab-dash': {
templateUrl: 'templates/tab-dash.html',
controller: 'DashCtrl'
}
}
})
.state('subscription', {
url: '/subscription',
templateUrl: 'templates/subscription.html',
controller: 'SubscriptionCtrl'
})
.state('login', {
url: '/login',
templateUrl: 'templates/login.html',
controller: 'LoginCtrl'
})
.state('test-compare', {
url: '/test/compare',
templateUrl: 'templates/test.html',
controller: 'TestCompareCtrl'
})
// if none of the above states are matched, use this as the fallback
$urlRouterProvider.otherwise('/login');
});
I don't understand why the url is changing I am giving /
and it test templates/test.html
which is always the last state template
4 Answers
Reset to default 7 +50Your main issue here is this line:
beforeEach(module('compare'));
You are loading your entire app here instead of just the apiClient. Essentially, you are doing a full blown integration test, instead of a unit test.
You should be loading only api.client
.
beforeEach(module('api.client'));
Something useful to note, you could also do something like:
$httpBackend.whenGET(/templates\/(.*)/).respond('');
which basically ignores all templates that get loaded by routers, controllers, or directives. If you do this though, it still wouldn't be considered a unit test, because you are not strictly testing just your APIClient
.
Another useful note:
Anything you execute inside of .run
or .config
should not be an annonymous function, that way you could mock it.
An example of this would be doing:
.config(CompareStateLoader);
CompareStateLoader.$inject = [
'$stateProvider',
'$urlRouterProvider'
];
function CompareStateLoader(
$stateProvider,
$urlRouterProvider
){
//configure states here
}
Doing this would allow you to mock CompareStateLoader
and load this in your test runner.
For more information on this, please see John Papa's Angular Style Guide here.
I would suggest to compile all your templates into JS file (for example with grunt "html2js" task or karma preprocessor "ng-html2js") and do not have headache with GETing templates.
Or you also could use passThrough
$httpBackend.when('GET', /\.html$/).passThrough()
Example - http://plnkr.co/edit/pbjcDl?p=preview
But I would suggest to use first option.
var apiClient, $httpBackend, $loc;
beforeEach(module('compare'));
beforeEach(inject(function($injector, $location) {
$httpBackend = $injector.get('$httpBackend');
apiClient = $injector.get('APIClient');
$loc = $location;
}));
it ('Should send GET request', function(done) {
expect($loc.path()).toEqual('');
var url = '/';
$httpBackend.expect('GET', $loc.path('/')).respond({});
apiClient.send({
url: url,
success: function(data, status) {
console.log(status);
done();
},
error: function(data, status) {
console.log(status);
done();
}
});
$httpBackend.flush();
});
Edit You should use angular-mock to use $location
beforeEach(inject(function(_$httpBackend_, APIClient) {
$httpBackend = _$httpBackend_;
apiClient = APIClient;
}));
Add this line in your before each block - $httpBackend.expect('GET', "templates/test.html").respond(200);
$httpBackend
in our tests it intercepts all http requests. So obviously you have some code (not from APIClient if this is the only code of it that is presented in OP) that triggers states. Can you provide code that definescompare
module? – Kirill Slatin Commented Jul 3, 2015 at 13:20APIClient
service inapi.client
module, but callcompare
in test suite for the service? – Kirill Slatin Commented Jul 3, 2015 at 14:22