I am new to writing unit tests, so apologies if this is a dumb question. If I'm trying to test a method in an Angular controller that relies on mock data from a service call, and I want to also mock that service call (ngResource), is there a way to make httpBackend ignore other requests made in my controller on initialization?
I've tried placing my whenGET or expectGET definitions in before blocks, and only instantiating my controller within my test, but I always find that httpBackend is expecting other requests (Error: Unexpected request) when I call flush(). I do not want to write mocks for all other requests, just the one I'm using for this test.
Of course, this may be a stupid idea, as I can also just provide the fake data directly, and not test the service along with the controller's method. I've verified that this works. Maybe the correct answer is that I shouldn't be testing services from within a controller.
FWIW, I've also tried using Sinon fakeServer, and apparently it doesn't even pick up on Angular's XHR implementation (the server never responds).
Related
I'm trying to test a controller that does an http request through a service.
Should I just mock the service and return a default value, rather than doing an actual http request, or using $httpBackend.
I'm testing in Jasmine by the way.
Thanks.
TL;DR Don't do an actual http-request.
UNIT-TESTS
When doing proper unit-tests, you only test a single unit. This can be a class or only a part of a class. This means that you have to mock the dependencies. In your case it would mean that you'd mock the service to simply act as the class would. So returning a promise containing a data-model.
pro: The biggest advantage of true unit-tests is speed. You can perform a huge amount of unit tests instead of a single end-to-end test.
con: The biggest disadvantage of unit-tests is that when you change a dependency to work in a different way, your tests will still succeed because the service has been mocked.
INTEGRATION-TESTS
An integration test works almost a a unit-test, but here you don't mock direct dependencies. In your case, when doing an integration-test, you would not mock the service, rather it's dependencies (with $httpBackend).
pro: Still quite fast, and offers even more robust tests. Because when you update the direct dependencies, the classes you test can fail because they are not mocked.
con: Not quite as fast as an unit-test, but still very fast.
END-TO-END
E2E tests test the entire application, not mocking anything. This includes all XHR-calls to an api.
pro: Since nothing is mocked, it always covers the entire application. And it is very useful to track DOM-changes and browser-compability. It can even automatically take screenshots to give a real view of the rendered data.
con: it's slow. Because it performs actual API-calls these tests can take a while to perform.
So to answer your question, it depends what you're writing. When you write proper unit-tests you should mock the service:
$provide.service('DataService', ['$q', function($q) {
this.get = function() {
return $q(function(resolve, reject) {
if (requestFailed) {
reject('The request failed');
}
resolve(APIData);
});
};
}]);
If you're doing integartion-tests, you should mock the actual $http-request using $httpBackend.
it('should request data', function() {
$httpBackend
.expect('GET', url)
.respond(APIData);
expect($scope.list.count).toEqual(0);
$scope.clickRetrieve();
$httpBackend.flush();
expect($scope.list.count).toBeGreaterThan(0);
});
I'm working on quite a big application an have not to much experience in testing. But my favorite testing-type is by far integration-testing. I've had some issues with unit-tests that didn't reveal breaking changes due to the mocked services. Since I've all but switched to integrated tests, where I mock almost exclusively my data-services.
side-note: I work using data-services which act as a layer between my application and the API, if the API is updated, in theory I should only update the data-service as no class except the repository accesses these.
This way I can ensure that in my application I only work with DataModels instead of simple Objects, and that I use undefined, not null.
use $httpBackend
Since you need to mock the response also
$httpBackend.when("GET",'URL').respond(respnonse);
response contains the value that you are expecting.
When expecting a call to the API should I include the entire URL including all the parameters, or do I just need a partial match?
Should I be listening for a call to the exact URL :
http://address.of.api/stuff/123?include=thing,anotherthing.name;
Or do I just need this :
/stuff/123
$httpBackend is part of Angular's Mock environment designed to replace real backend by fake one, or rather by imitating how Angular's $http works without real backend.
As much as I love Angular, I find $httpBackend over-engineered and unnecessarily complicated for what it does:
It is not recommended way to throw complicated code inside your tests. The more you do it, the more chance is that you create errors in that testing code instead of what you are actually supposed to test.
It promotes the bad practice of placing $http (or other low-level services in your abstraction hierarchy) freely around your code, as you can later use that $httpBackend to mock it away.
Instead it works cleaner to isolate any reference to low level methods into dedicated methods of your own, whose single responsibility is to make http requests. These dedicated methods should be able to work with real backend, not a fake one!
More details here on Angular testing
Im trying to understand Angularjs. I have a controller that contains one function and one http.get. Is it possible to write a Karma unittest for the function without mocking $httpbackend? Right now i get the error unexpected http call and the test newer runs.
If you are using angular-mocks.js, which I assume you are as you're using Karma, $httpBackend is already mocked. You just need to define your expectations, which can be very loose. Like so:
$httpBackend.when('GET', '/expected-url').respond(200, '');
If you want to use the real $httpBackend, don't include ngMock, but I won't recommend that :)
I'm building an app based in Breeze and Angular
They work pretty well together but the unit test is a problem.
This is a pretty vanilla test but Breeze keeps getting in the middle:
describe('myController', function () {
beforeEach(inject(function ($injector) {
module('app');
$httpBackend = $injector.get('$httpBackend');
authRequestHandler = $httpBackend.whenGET().respond(200,
{"someStrings": ["foo", "bar"]})
//more uninteresting code...
createController = function () {
return $controller('myController', { '$scope': $rootScope });
};
}));
it('should fetch authentication token', function () {
$httpBackend.expectGET('/auth.py');
var controller = createController();
$httpBackend.flush();
});
The problem is that Breeze keeps being initialized. At execution, I receive the following message:
Error: cannot execute _executeQueryCore until metadataStore is populated.
//or,with different get: ... $httpBackend.when('GET', '/auth.py')
// .respond({ userId: 'userX' });
Error: Unexpected request: GET breeze/Breeze/Metadata No more request expected
How do I prevent or mock or stub Breeze so doesn't interfere with my tests... For instance, these tests are aimed to authentication, not data.
Breeze is not "getting in the middle" on its own. Breeze would not get involved in your $http authorization call. I'll eat my hat if you can show me that it does. You haven't shown that it does here.
But you have surfaced a very interesting point about application bootstrap design and the consequences of that design for testing.
Evidently, either your app module's start method or your controller's creation logic executes a Breeze query (perhaps both of them do). I deduce this from two facts:
The exception comes from executeQueryCore which only happens when you explicitly execute a Breeze query
You don't touch the controller in your test, neither in the beforeEach nor in the it which means these calls (and your auth call too) are made by some kind of automatic startup logic that executes before your it spec.
In your test you have taken the trouble to mock the auth call (which is in your startup logic somewhere) but not the Breeze calls.
I don't know what you actually want to test. Why would you test that the controller fetches an auth token? Is that really the controller's concern?
Perhaps you present this test merely to illustrate the problems you're having testing a controller without getting the real server involved?
Let me step back and make a more important and more general point. We must be wary of automatic startup logic whether it hides in an app module start or a controller's constructor. Be wary in particular of startup logic that involves calls to the server.
I tend to disable automated startup logic in most of my tests. I often substitute test doubles for the troublesome dependent services during my test module setup ... before calling ngMock's inject function. I make sure that the app.start method's callback ONLY uses dependent services that are easy to fake.
I you want to forge ahead using the actual dependencies by mocking the HTTP responses with $httpBackend, then you'll have to prepare $httpBackend for every request it receives from the startup code ... including the requests YOU are making with Breeze.
I'll end by reiterating that Breeze only does what you tell it to do. It is completely unaware of your direct-to-$http calls.
The Angularjs tutorial shows something using the $http service and then testing that using the $httpBackend mock. What it doesn't explain is why you mock $httpBackend and not just mock the $http service itself? Can anyone shed light of this?
You don't mock $httpBackend. You use it to mock the actual HTTP requests that $http makes. I suppose you probably could just mock $http itself, but $httpBacked provides a lot of functionality for asserting certain requests are made (The expect methods) and for just dummying in a response (The when methods). In short, $httpBacked makes testing code that uses $http much much easier.