Please see my code example below, I have a protractor test that get's run as a GULP task by angular-protractor.
I want to be able to mock some REST calls for all it blocks, but also have access to the httpBackend for specific it blocks.
I'm having trouble sharing the $httpBackend variable around though:
I can't use this.VARIABLE_NAME because of the scope I am in inside the run function.
Code Example
describe('Train Station Search Component', function() {
beforeEach(function() {
//Mock any REST calls here.
browser.addMockModule('httpBackendMock', function() {
angular.module('httpBackendMock', ['MyApp', 'ngMockE2E'])
.run(function($httpBackend) {
//Mock call relevant to both it blocks.
$httpBackend.whenPOST(/^(.*\/api\/search)/).respond({...});
});
});
);
it('should check one piece of functionality', function() {
//Expect call with data relevant only for this it block.
$httpBackend.expectPOST(...);
});
it('should check another piece of functionality', function() {
//Expect call with data relevant only for this it block.
$httpBackend.expectPOST(...);
});
};
Any help appreciated.
You can use $injector to inject $httpBackEnd service.
var $httpBackend
beforeEach(inject(function($injector) {
// Set up the mock http service responses
$httpBackend = $injector.get('$httpBackend');
});
Go through the link https://docs.angularjs.org/api/ngMock/service/$httpBackend
Related
I am trying to test my controller using jasmine. Basically, when the controller is created it will call a service to make http request. I am using httpBackend to get the fake data. When I try to run the test I always get the error "No pending request to flush". If I remove the httpBackend.flush() then the test fails because controller.data.name is undefined. Can anyone know why it happens like that? Thanks.
The code for the module is here:
var myModule = angular.module('myModule', ['ngMockE2E']);
myModule.run(function($httpBackend){
$httpBackend.whenGET('/Person?content=Manager').respond(function (){
var response = {'name':'Bob','age':'43'}
return [200,response];
})
});
The code for the service:
myModule.factory('myService',function($http){
return {
getData: function(position){
return $http.get('/Person?content='+position);
}
}
});
The code for controller is:
myModule.controller('myController',function(xrefService){
var _this = this;
_this.data ={};
_this.getData = function(position){
myService.getData(position).then(function(response){
_this.data = response.data
});
}
_this.getData("Manager");
})
The code to test the controller is:
describe("Test Controller",function(){
var controller,httpBackend,createController;
beforeEach(module('myModule'));
beforeEach(inject(function($controller,$httpBackend){
createController = function(){
return $controller('myController');
}
httpBackend = $httpBackend;
}));
it("should return data",function(){
controller = createController();
httpBackend.flush();
expect(controller.data.name).toEqual("Bob");
});
})
The angular documentation says the following about $httpbackend for ngMockE2E:
Additionally, we don't want to manually have to flush mocked out
requests like we do during unit testing. For this reason the e2e
$httpBackend flushes mocked out requests automatically, closely
simulating the behavior of the XMLHttpRequest object.
So, short answer: it doesn't exist and you don't need it.
you are using $httpBackend.whenGET inside "The code for the module"
you should be using $httpBackend inside the test code as follows ...
it("should return data",function(){
$httpBackend.expectGET('/Person?content=Manager').respond(function (){
var response = {'name':'Bob','age':'43'}
return [200,response];
})
controller = createController();
httpBackend.flush();
expect(controller.data.name).toEqual("Bob");
});
also i would advise using expectGET instead of whenGET.
With whenGET you are saying if the request is made then response like so.
With expectGET you are saying ... a request will be made, when it is made respond like so, if the request is not made then fail the test.
PS if you put some console.log statements inside your controller code then you should see these log statements when you run your test suite. If not then you know your controller code is not even being hit.
also use ..
afterEach(function () {
httpBackend.verifyNoOutstandingExpectation();
httpBackend.verifyNoOutstandingRequest();
});
which will force test failure if expectations were not met.
I'm looking to write a Jasmine unit test which executes a callback function passed to a then function. This then function is chained to a call to the AngularJS $http service, and it's inside a custom service. Here's the code I'm working with:
app.service('myService', function($rootScope, $http) {
var service = this;
var url = 'http://api.example.com/api/v1/resources';
service.resources = {
current: []
};
service.insertResource = function (resource) {
return $http.post(url, resource).then(function(response){
$rootScope.$broadcast('resources:updated', service.resources.current);
return response;
});
};
});
Here's my attempt to write a test which executes this callback, but to no avail:
describe('resource service', function() {
beforeEach(angular.mock.module('myapp'));
var resourceService;
beforeEach(inject(function(_resourceService_) {
resourceService = _resourceService_;
}));
it('should insert resources', function() {
resourceService.insertResource({});
});
});
There are several approaches you could take:
Use $httpBackend.expectPOST
Use $httpBackend.whenPOST
Move the code in the callback to a named function (not an anonymous one) and write a test for this function. I sometimes take this route b/c I don't want the trouble of writing tests with $httpBackend. I only test the callback function, I don't test that my service is calling the callback. If you can live w/that it's much simpler approach.
Check the documentation for $httpBackend for details. Here's a simple example:
describe('resource service', function() {
beforeEach(angular.mock.module('myapp'));
var resourceService, $httpBackend;
beforeEach(inject(function($injector) {
resourceService = $injector.get('resourceService');
$httpBackend = $injector.get('$httpBackend');
}));
afterEach(function() {
// tests will fail if expected HTTP requests are not made
$httpBackend.verifyNoOutstandingRequests();
// tests will fail if any unexpected HTTP requests are made
$httpBackened.verifyNoOutstandingExpectations();
});
it('should insert resources', function() {
var data: { foo: 1 }; // whatever you are posting
// make an assertion that you expect this POST to happen
// the response can be an object or even a numeric HTTP status code (or both)
$httpBackend.expectPOST('http://api.example.com/api/v1/resources', data).respond({});
// trigger the POST
resourceService.insertResource({});
// This causes $httpBackend to trigger the success/failure callback
// It's how you workaround the asynchronous nature of HTTP requests
// in a synchronous way
$httpBackend.flush();
// now do something to confirm the resource was inserted by the callback
});
});
I am trying to perform unit testing with Karma. I have done everything according to the documentation. When I write this part of the test that follows it never calls the last two functions.
it('should create the mock object', function (done) {
service.createObj(mockObj)
.then(test)
.catch(failTest)
.finally(done);
});
var test = function() {
expect(2).toEqual(1);
};
var failTest = function(error) {
expect(2).toEqual(1);
};
Try to inject into your beforeEach function rootScope. For example like this:
var rootScope;
beforeEach(inject(function (_$rootScope_) {
rootScope = _$rootScope_.$new();
//other injections
}));
and next invoke $digest() after your service method:
it('should create the mock object', function (done) {
service.createObj(mockObj)
.then(test)
.catch(failTest)
.finally(done);
rootScope.$digest();
});
Install angular-mocks module.
Inject module with module in beforeEach.
Inject your service with inject function in beforeEach.
Use $httpBackend to simulate your server.
Do, not forget, to make it, sync. with $http.flush().
I want to make an integration test with real calls to my server, so, I don't want to use the $httpBackend module from angular-mocks, So I try this:
beforeEach(inject(function($rootScope,_MembersDataSvc_){
service = _MembersDataSvc_;
}));
it('test',function(done){
service.me().then(function(){done();});
});
And the service is:
function me() {
return $http
.get('urlBase/me')
.then(meSuccess);
function meSuccess(response) {
return response.data.members[0];
}
}
This never call the $http, it seems that angular-mocks override the $http service an never made the call.
Some ideas?
EDIT 1:
According to this post: http://base2.io/2013/10/29/conditionally-mock-http-backend/
you can make a passThrough for that $http calls that you don't want to mock, so y try this:
var service;
var scope;
var $httpBackend;
beforeEach(inject(function($rootScope,_MembersDataSvc_,_$httpBackend_){
service = _MembersDataSvc_;
scope = $rootScope.$new();
$httpBackend = _$httpBackend_;
}));
it('test',function(done){
//this.timeout(10000);
$httpBackend.whenGET(/views\/\w+.*/).passThrough();
$httpBackend.whenGET(/^\w+.*/).passThrough();
$httpBackend.whenPOST(/^\w+.*/).passThrough();
service.me().then(function(response){console.log(response);done();});
scope.$apply();
//service.getDevices(member).then(function(response){console.log(response);done();})
});
But the passThrough is undefined here.
EDIT 2:
I read this post: http://blog.xebia.com/2014/03/08/angularjs-e2e-testing-using-ngmocke2e/, but I supose that is an stanalone test??, I want to run with karma and jasmine.
This is my entire test.
describe('integration test', function () {
beforeEach(function () {
module('MyAngularApp');
});
var service;
var scope;
var $httpBackend;
beforeEach(inject(function($rootScope,_MembersDataSvc_,_$httpBackend_){
service = _MembersDataSvc_;
scope = $rootScope.$new();
$httpBackend = _$httpBackend_;
}));
it('test for test',function(done){
$httpBackend.whenGET(/views\/\w+.*/).passThrough();
$httpBackend.whenGET(/^\w+.*/).passThrough();
$httpBackend.whenPOST(/^\w+.*/).passThrough();
service.me().then(function(response){console.log(response);done();});
scope.$apply();
});
});
I recomend using ngMidwayTester that allows you to connect to the real backend, I use it to make integration tests on the code level - so something in between unit and e2e testing:
Two types of tests in AngularJS (plus one more) - Full-Spectrum Testing with AngularJS and Karma
I am writing unit tests for a controller. This controller has a $resource service injected :
function controller($scope, Service) {
Service.get(function(result){
// do stuff with the result, not relevant here
}
}
The service is defined like this :
angular.module('so').factory('Service', ['$resource', service]);
function service($resource) {
return $resource('/url', null, {
get: { method: 'POST', params: {}, isArray: false}
});
}
My Jasmine unit test is the following :
describe("Controller", function(){
var $httpBackend;
beforeEach(function() {
module('so');
inject(function( _$httpBackend_) {
$httpBackend = _$httpBackend_;
});
});
it('should have done stuff irrelevant to the question', function() {
var $injector = angular.injector('so'),
$scope = $injector.get('$rootScope'),
$httpBackend
.whenPOST('/url')
.respond ([]);
// controller needs to be defined here and not in the beforeEach as there
// are more parameters passed to it, depending on the test
var controller = $injector.get('$controller')('controller', { "$scope": $scope });
$httpBackend.flush();
// then here the actual test resolution, also irrelevant
});
});
I get an error when running the test :
Error: No pending request to flush ! in file:///path/to/angular-mock.js (line 1453)
I added a console.log() in the callback from Service.get() and indeed, it is not called (everything outside of the callback is of course called). Also tried to add a scope digest if not phased after controller creation in the unit test, as I saw suggested in an other question, with no luck.
I know that I can mock that in some other ways, but using $httpBackend seems the perfect solution for the test : mocking the webserver and the data received.
I'm using AngularJS 1.2.16 (can't upgrade to 1.3.*, IE 8 compatibility required). I first used 1.2.13 and updated to check if it would solve the issue, without any luck.
That was an injection issue that was solved by changing the test from
it('should have done stuff irrelevant to the question', function() {
var $injector = angular.injector('so'),
$scope = $injector.get('$rootScope'),
$httpBackend
.whenPOST('/url')
.respond ([]);
// controller needs to be defined here and not in the beforeEach as there
// are more parameters passed to it, depending on the test
var controller = $injector.get('$controller')('controller', { "$scope": $scope });
$httpBackend.flush();
// then here the actual test resolution, also irrelevant
});
To:
it('should have done stuff irrelevant to the question', inject(function(Service) {
// edited lines because they did not change
var controller = $injector.get('$controller')('controller', { "$scope": $scope, "Service": Service });
// edited lines because they did not change
}));
So basicaly, adding the inject() in the test function and passing the service to the controller "manually".
I found the issue, that's great, but I don't really understand why it doesn't work. Also, I tried this right after finding the solution :
it('should have done stuff irrelevant to the question', inject(function() {
// edited lines because they did not change
var Service = $injector.get('Service'),
var controller = $injector.get('$controller')('controller', { "$scope": $scope, "Service": Service });
// edited lines because they did not change
}));
but this fail again, with the same "no pending request" error. I'm guessing that's some sort of racing issue, where my service can't get the proper $httpBackend to be injected when it's created afterwards, but I don't really understand why this occurs. If anybody can enlighten me... I'll be grateful.