How do I expect an HTTP request NOT to be made? - angularjs

Angular's $httpBackend service lets you expect an HTTP request with expectGET, expectPOST, etc. (or just expect).
How would I write a test that says, "the controller should NOT make a request to this endpoint (under these conditions)"?
I was thinking something like:
$httpBackend.when('/forbidden/endpoint').respond(function() {
throw Error("Shouldn't be making a request to /forbidden/endpoint!");
});
That seems a bit hacky to me, but I'm fine with it if that's the normal way to do things. (But I doubt that.)

I stumbled over the same issue.
The solution would be to have a callback function as response and inside you could expect(true).toBe(false) or in my opinion something a little bit more beautiful:
it ('should not trigger HTTP request', function() {
var forbiddenCallTriggered = false;
$httpBackend
.when('/forbidden/endpoint')
.respond(function() {
forbiddenCallTriggered = true;
return [400, ''];
});
// do whatever you need to call.
$rootScope.$digest();
$httpBackend.flush();
// Let test fail when request was triggered.
expect(forbiddenCallTriggered).toBe(false);
});

For scenarios like this I often use Jasmine's spyOn() function. You can spy on functions of $http, $resource, or of a custom service (like myServiceThatUsesHTTP below):
spyOn(myServiceThatUsesHTTP, 'query');
// test, then verify:
expect(myServiceThatUsesHTTP.query).not.toHaveBeenCalled();
// or
expect(myServiceThatUsesHTTP.query.callCount).toBe(0);
When you spyOn() a function, the original function is replaced. The code for the original function is not executed, which can be good or bad (depending on what you need to do for the test).
For example, if you need the $promise object that $http or $resource returns, you can do this:
spyOn($http, '$get').andCallThrough();

One solution might be to check if $httpBackend.flush() throws an exception, since there should be nothing to flush:
beforeEach(function() {
$httpBackend.whenGET('/forbidden/endpoint');
...
// call service method under test (that should not make $http call)
});
it('Should not call the endpoint', function() {
expect($httpBackend.flush).toThrow();
});
Important thing to note: we use when and not expect, since we don't actually expect the call to be made. And since there is no call, $httpBackend.flush() will throw an exception: No pending request to flush.

$httpBackend is not applied because the $http call doesn't get made in this test.
Instead, you can inject $http in your test, and then spyOn() $http directly:
beforeEach(fn () {
inject(function ($injector) {
this.service = $injector.get('serviceOrControllerOrDirectiveBeingTested');
this.$http = $injector.get('$http');
}
});
and then
it('should ...', fn() {
spyOn(this.$http, 'get');
this.service.methodThatTriggersTheHttpCall();
expect(this.$http.get).not.toHaveBeenCalled();
});

Related

Delay testing of a value until a promise has resolved

I'm attempting to test that a value is changed to true after a promise is resolved inside $onInit. I'm following, as best I can, the example in this Stack Overflow question/answer. Here is my code:
class TestCtrl {
constructor(SearchService) {
this.testValue = false;
this.SearchService = SearchService;
}
$onInit() {
this.SearchService.getResults()
.then(function () {
this.testValue = true;
});
}
}
TestCtrl.$inject = ['SearchService'];
And here's the test I'm attempting to run (using mocha, chai, sinon):
it('should work', function() {
ctrl = $componentController('test', {
SearchService: SearchService
}, {});
sinon.stub(SearchService, 'getResults').resolves({response:{data: 'data'}});
ctrl.$onInit();
$rootScope.$apply();
ctrl.testValue.should.equal(true);
});
Should I be testing ctrl.testValue inside a then? Also, is using this example a bad idea because that example doesn't use a component with an $onInit lifecycle hook?
From what I've read, no, "don't use expect inside then in tests." But I'm not so sure based on what I've read elsewhere.
I wouldn't be surprised if I'm missing something obvious in how to test promises (maybe a stub wasn't the way to go?) and/or how to test what happens in the $onInit lifecycle hook.
If the question needs more details, please ask and I'll do my best to add them.
Edit: Checkout you $onInit method:
$onInit() {
this.SearchService.getResults()
.then(function () {
// `this` in anonymous function is reffering to window not the controller instance
this.testValue = true;
});
}
$onInit() {
var self = this;
self.SearchService.getResults()
.then(function () {
self.testValue = true;
});
}
Your example is correct
This is the way to test async code in angularjs - it is tested like synchronous code. Stubs' returning promises are resolved when you execute $rootScope.$apply().
Why it doesn't work
The promise returned from stub.resolves() is not an angular promise. It cannot be triggered to resolve using $rootScope, because it's not a part of angular's world. It's promise resolution queue is tied to something else and hence the need to test like you usually test async code.
Angular doesn't depend on JavaScript's native Promise implementation - it uses a light implementation of Q's promise library that is wrapped in a service called $q
The answer you have quoted uses the same service to create and return a promise from a stub
In order for your code to work - to test like you test synchronous code - you should return a $q promise (By wrapping a value in $q.when(value)) calling $rootScope.$apply() will execute the code in the then block, then proceed with the code below $rootScope.$apply() line.
Here is an example:
it('Sinon should work with angular promises', function () {
var resolved = 'resolved';
var promise = $q.when(resolved);
// Our async function
var stub = sinon.stub().returns(promise);
// Callback to be executed after the promise resolves
var handler = sinon.stub();
stub().then(handler); // async code
// The handler will be called only after $rootScope.$apply()
handler.callCount.should.equal(0);
// triggers digest which will resolve `ready` promises
// like those created with $q.when(), $q.resolve() or those created
// using the $q.defer() and deferred.resolve() was called
// this will execute the code inside the appropriate callback for
// `then/catch/finally` for all promises and then continue
// with the code bellow -> this is why the test is considered `synchronous`
$rootScope.$apply();
// Verify the handler was called and with the expected value
handler.callCount.should.equal(1);
handler.should.have.been.calledWith(resolved);
})
Here it is in action test promise synchronously in angular
For starters, you should read up on how Mocha expects you to test async code.
To start out with the quick bits:
You are on the right path - there are just some bits missing.
Yes you should do your test inside a then.
The example you linked to is fine. Just understand it.
There is absolutely no reason to avoid asserting a test inside a then. In fact, it is usually the only way to assert the resolved value of a promise.
The main problem with your test code is it tries to assert the result before it is available (as promises resolve in a later tick, they are asynchronous).
The main problem with the code you are trying to test is that there is no way of knowing when the init function has resolved.
We can deal with #2 by waiting for the stubbed SearchService.getResults to resolve (as we control the stub in the test), but that assumes too much knowledge of the implementation of onInit, so that is a bad hack.
Instead, we fix the code in TestCtrl, simply by returning the promise in onInit:
//main code / TestCtrl
$onInit() {
return this.SearchService.getResults()
.then(function () {
this.testValue = true;
});
}
Now we can simply wait for any call to onInit to resolve before we test what has happened during its execution!
To fix your test we first add a parameter to the wrapping test function. Mocha will see this and pass in a function that you can call when your test finishes.
it('should work', function(done) {
That makes it an async test. Now lets fix the test part:
ctrl.$onInit().then( () => {
ctrl.testValue.should.equal(true);
done(); // signals to mocha that the test is finished ok
}).catch(done); // pass any errors to the callback
You might find also find this answer enlightening (upvote if it helps you out). After reading it you might also understand why Mocha also supports dropping the done callback by returning a promise from the test instead. Makes for shorter tests:
return ctrl.$onInit().then( () => {
ctrl.testValue.should.equal(true);
});
sinon.stub(SearchService, 'getResults').resolves({response:{data: 'data'}}); is not returning a promise. Use $q.
I would suggest doing this:
ctrl = $componentController('test', {
SearchService: SearchService
}, {});
let deferred =$q.defer();
deferred.resolve({response:{data: 'data'}});
sinon.stub(SearchService, 'getResults').resolves(deferred.promise);
ctrl.$onInit();
$rootScope.$apply();
ctrl.testValue.should.equal(true);
You don't need to test ctrl.testValue inside a then. And generally, I would recommend not assert inside .then() in your specs. The specs will not fail if the promise never gets resolved. That can give you a false sense of security when in reality, your tests are not doing anything. But that's just my opinion.
Your test will pass once the stub returns a promise. Ideally, I would recommend using $httpBackend if the service is making an http call.
Cheers.

How to test an AngularJS controller value that is set within a promise using Sinon

I'm having troubling testing a controller's value that's set within a promise returned by a service. I'm using Sinon to stub the service (Karma to run the tests, Mocha as the framework, Chai for assertions).
I'm less interested in a quick fix than I am in understanding the problem. I've read around quite a bit, and I have some of my notes below the code and the test.
Here's the code.
.controller('NavCtrl', function (NavService) {
var vm = this;
NavService.getNav()
.then(function(response){
vm.nav = response.data;
});
})
.service('NavService', ['$http', function ($http) {
this.getNav = function () {
return $http.get('_routes');
};
}]);
Here's the test:
describe('NavCtrl', function () {
var scope;
var controller;
var NavService;
var $q;
beforeEach(module('nav'));
beforeEach(inject(function($rootScope, $controller, _$q_, _NavService_){
NavService = _NavService_;
scope = $rootScope.$new();
controller = $controller;
}));
it('should have some data', function () {
var stub = sinon.stub(NavService, 'getNav').returns($q.when({
response: {
data: 'data'
}
}));
var vm = controller("NavCtrl", {
$scope: scope,
NavService: NavService
});
scope.$apply();
stub.callCount.should.equal(1);
vm.should.be.defined;
vm.nav.should.be.defined;
});
});
The stub is being called, i.e. that test passes, and vm is defined, but vm.nav never gets data and the test fails. How I'm handling the stubbed promise is, I think, the culprit. Some notes:
Based on reading elsewhere, I'm calling scope.$apply to set the value, but since scope isn't injected into the original controller, I'm not positive that will do the trick. This article points to the angular docs on $q.
Another article recommends using $timeout as what would "actually complete the promise". The article also recommends using "sinon-as-promised," something I'm not doing above. I tried, but didn't see a difference.
This Stack Overflow answer use scope.$root.$digest() because "If your scope object's value comes from the promise result, you will need to call scope.$root.$digest()". But again, same test failure. And again, this might be because I'm not using scope.
As for stubbing the promise, I also tried the sinon sandbox way, but results were the same.
I've tried rewriting the test using $scope, to make sure it's not a problem with the vm style, but the test still fails.
In the end, I could be wrong: the stub and the promise might not be the problem and it's something different and/or obvious that I've missed.
Any help is much appreciated and if I can clarify any of the above, let me know.
Sorry but a quick fix was all that you needed:
var stub = sinon.stub(NavService, 'getNav').returns($q.when({
response: {
data: 'data'
}
}));
Your promise is resolved to object containing response.data not just data
Checkout this plunk created from your code: https://plnkr.co/edit/GL1Xuf?p=preview
The extended answer
I have often fallen to the same trap. So I started to define the result returned from a method separately. Then if the method is async I wrap this result in a promise like $q.when(stubbedResult) this allow me to, easily run expectations on the actual result, because I keep the stubbed result in a variable e.g.
it('Controller should have some data', function () {
var result = {data: 'data'};
var stub = sinon.stub(NavService, 'getNav').returns($q.when(result));
var vm = controller(/* initController */);
scope.$apply();
stub.callCount.should.equal(1);
vm.nav.should.equal(result.data)
})
Also some tests debugging skill will come in handy. The easiest thing is to dump some data on the console just to check what's returned somewhere. Working with an actual debugger is preferable of course.
How to quickly catch mistakes like these:
Put a breakpoint at the $rootScope.apply() line (just before it is executed)
Put a breakpoint in the controller's NavService.getNav().then handler to see whether it is called and what it was called with
Continue with the debugger to execute the $rootScope.$apply() line. Now the debugger should hit the breakpoint set at the previous step - that's it.
I think you should use chai-as-promised
and then assert from promises like
doSomethingAsync().should.eventually.equal("foo");
or else use async await
it('should have some data', async function () {
await scope.$apply();
});
you might need to move then getNav() call in init kinda function and then test against that init function

Unable to use httpBackend flush for ngMockE2E

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.

How do I mock angular's $http with sinon?

I am trying to do a simple mock of angular's $http with sinon in a Mocha test.
But my spy never has any results in it no matter what I try.
searchResource.typeAhead is my function under test. It calls $http based on its arguments and I want to make sure the request is correct.
searchResource.typeAhead returns a promise, but I tried putting the checking code in .then() and it never executes.
suite('Search Resource', function() {
var injector = angular.injector(['cannonball-client-search', 'cannonball-client-core']);
var searchResource = injector.get('SearchResource');
suite('#typeAhead()', function () {
setup(function () {
this.server = sinon.fakeServer.create();
this.server.respondWith('GET',
config.endpoints.search.typeAhead,
[200, {'Content-Type': 'application/json'}, '[{ "id": 12, "comment": "Hey there" }]']);
this.spyhttp = sinon.spy(injector.get('$http'));
});
teardown(function () {
this.server.restore();
});
test('positive', function (done) {
searchResource.typeAhead(
'expl',
[{entityType: 'itementity'}],
[{createdBy: 'Eric'}, {createdBy: 'Tal'}],
10
);
this.server.respond();
expect(this.spyhttp.calledWith({
method: 'get',
url: config.endpoints.search.typeAhead +
'?query=expl&filter=entityType:itementity&orfilter=createdBy:Eric&orfilter=createdBy:Tal&limit=10'
})).to.be.true();
done();
});
});
});
The problem lies outside of Sinon mocking.
If angular.injector is used directly instead of suggested angular.mock.module and angular.mock.inject helpers, the one is on his own with it and his knowledge of Angular injector.
The obvious downside is that the injector won't be torn down automatically after each spec (while it would be when angular.mock.module is used), so all nested specs operate on the same instance of Angular injector.
At this point
var searchResource = injector.get('SearchResource');
SearchResource service instance was already injected with unmocked $http, that's the end of the story. Even if it wouldn't, there's no chance that Angular will ever know that this.spyhttp spy should be used instead of original $http service. Its methods can be spied after the instantiation
sinon.spy($http, 'get');
but not $http function itself.
The strategy for testing with angular.injector may be
var $httpSpy;
var injector = angular.injector([
'cannonball-client-search',
'cannonball-client-core',
function ($provide) {
$provide.decorator('$http', function ($delegate) {
return ($httpSpy = sinon.spy($delegate));
});
}
]);
// injector.get('$http') === $httpSpy;
Notice that this will make Sinon spy on $http function, not on its methods.
If the question is about how Angular mocks should be approached with Sinon, then it's as easy as that. Otherwise this may indicate an XY problem, and the other answer addresses it directly ($httpBackend and the way $http embraces it are there exactly to make the burden of mocking XMLHttpRequest requests non-existent).
Angular was built with testing in mind. The previous comments aren't suggesting that you cannot use sinon to mock out $http, its just not common practice and it definitely won't be as easy to do as it is with $httpBackend.
I would personally only be using sinon to mock any dependencies which don't belong to Angular as such. It's easy enough to provide mock responses with $httpBackend:
$httpBackend.when('GET', '/url').respond({
mock: 'response'
});
Now any request to '/url' with use the mock response object. I'm sure $httpBackend has some other complicated wizardry built in to handle other things like interceptors perhaps?

How to resolve promises in AngularJS, Jasmine 2.0 when there is no $scope to force a digest?

It seems that promises do not resolve in Angular/Jasmine tests unless you force a $scope.$digest(). This is silly IMO but fine, I have that working where applicable (controllers).
The situation I'm in now is I have a service which could care less about any scopes in the application, all it does it return some data from the server but the promise doesn't seem to be resolving.
app.service('myService', function($q) {
return {
getSomething: function() {
var deferred = $q.defer();
deferred.resolve('test');
return deferred.promise;
}
}
});
describe('Method: getSomething', function() {
// In this case the expect()s are never executed
it('should get something', function(done) {
var promise = myService.getSomething();
promise.then(function(resp) {
expect(resp).toBe('test');
expect(1).toEqual(2);
});
done();
});
// This throws an error because done() is never called.
// Error: Timeout - Async callback was not invoked within timeout specified by jasmine.DEFAULT_TIMEOUT_INTERVAL.
it('should get something', function(done) {
var promise = myService.getSomething();
promise.then(function(resp) {
expect(resp).toBe('test');
expect(1).toEqual(2);
done();
});
});
});
What is the correct way to test this functionality?
Edit: Solution for reference. Apparently you are forced to inject and digest the $rootScope even if the service is not using it.
it('should get something', function($rootScope, done) {
var promise = myService.getSomething();
promise.then(function(resp) {
expect(resp).toBe('test');
});
$rootScope.$digest();
done();
});
You need to inject $rootScope in your test and trigger $digest on it.
there is always the $rootScope, use it
inject(function($rootScope){
myRootScope=$rootScope;
})
....
myRootScope.$digest();
So I have be struggling with this all afternoon. After reading this post, I too felt that there was something off with the answer;it turns out there is. None of the above answers give a clear explanation as to where and why to use $rootScope.$digest. So, here is what I came up with.
First off why? You need to use $rootScope.$digest whenever you are responding from a non-angular event or callback. This would include pure DOM events, jQuery events, and other 3rd party Promise libraries other than $q which is part of angular.
Secondly where? In your code, NOT your test. There is no need to inject $rootScope into your test, it is only needed in your actual angular service. That is where all of the above fail to make clear what the answer is, they show $rootScope.$digest as being called from the test.
I hope this helps the next person that comes a long that has is same issue.
Update
I deleted this post yesterday when it got voted down. Today I continued to have this problem trying to use the answers, graciously provided above. So, I standby my answer at the cost of reputation points, and as such , I am undeleting it.
This is what you need in event handlers that are non-angular, and you are using $q and trying to test with Jasmine.
something.on('ready', function(err) {
$rootScope.$apply(function(){deferred.resolve()});
});
Note that it may need to be wrapped in a $timeout in some case.
something.on('ready', function(err) {
$timeout(function(){
$rootScope.$apply(function(){deferred.resolve()});
});
});
One more note. In the original problem examples you are calling done at the wrong time. You need to call done inside of the then method (or the catch or finally), of the promise, after is resolves. You are calling it before the promise resolves, which is causing the it clause to terminate.
From the angular documentation.
https://docs.angularjs.org/api/ng/service/$q
it('should simulate promise', inject(function($q, $rootScope) {
var deferred = $q.defer();
var promise = deferred.promise;
var resolvedValue;
promise.then(function(value) { resolvedValue = value; });
expect(resolvedValue).toBeUndefined();
// Simulate resolving of promise
deferred.resolve(123);
// Note that the 'then' function does not get called synchronously.
// This is because we want the promise API to always be async, whether or not
// it got called synchronously or asynchronously.
expect(resolvedValue).toBeUndefined();
// Propagate promise resolution to 'then' functions using $apply().
$rootScope.$apply();
expect(resolvedValue).toEqual(123);
}));

Resources