Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why should I mock HTTP requests inside unit tests?

I'm working on a project and we've begun to write Jasmine unit tests. This application, like any good JS app does a lot of asynchronous fetching of data. I see that angular provides $httpBackend to mock up an HTTP request. I've also read and heard that it's a bad idea to test AJAX requests in your controller, and thus the raison d'etre for $httpBackend. Why is it not a good idea to test AJAX calls? How do big JS applications get around this fact? When does the actual testing of hitting the actual servers happen?

like image 363
wootscootinboogie Avatar asked Jun 13 '14 00:06

wootscootinboogie


2 Answers

Asserting that you shouldn't be testing the server from Jasmine is an overly simple generalization. Whether you want small tests, large tests, or some combination of both really depends on the application. One might say that it isn't a "unit" test anymore - that it's an "integration" test - but those terms have been overloaded to the point of nonsense because it's a rather artificial dichotomy. Write tests that are useful and maintainable. Choosing the size of the test is an important part of that. What size is appropriate requires your own judgement of the situation.

like image 118
Chris Martin Avatar answered Oct 22 '22 02:10

Chris Martin


The idea of a unit test is to outline how a small part of an application should work. If it is passing that test, you know that it is functioning as specified.

If you want to test a service that sends requests to your API, you set up unit tests that check the location/content/existence of the outgoing requests and verifies that it responds correctly to a valid response. To make the test as specific as possible though, it should not test anything outside of that little bubble.

Let's say we want to test this PersonService angular service:

app.service('PersonService', function ($q, $timeout, $http) {
    this.addPerson = function(data) {
        var deferred = $q.defer();
        $http.post('api/People', data).then(function(response) {
            deferred.resolve(response);
        });
        return deferred.promise;
    };
});

Here is a simple Jasmine test:

var service, $httpBackend;
beforeEach(module('app'));
beforeEach(inject(function(_PersonService_, _$httpBackend_) {
    service = _PersonService_;
    $httpBackend = _$httpBackend_;
}));

describe('addPerson', function() {
    var person;
    beforeEach(function() {
        // Make a person to send
        person = new Person({ id: "0ff1165f-7f75-45ed-8faa-ee94d874a1cf" });
    });

    afterEach(function() {
        $httpBackend.verifyNoOutstandingExpectation();
        $httpBackend.verifyNoOutstandingRequest();
    });

    it('should send a person object to the API', function() {
        // setup
        var promise, expected, actual;
        expected = /* Whatever is sent back */
        $httpBackend.whenPOST('api/People', person)
            .respond(expected);

        // act
        promise = service.addPerson(person);
        promise.then(function(response) {
            actual = response.data;
        });

        $httpBackend.flush();

        // assert
        expect(actual).toEqual(expected);
    });
});

So we have a test that checks this very basic functionality (regardless of what the API actually does). This allows us to test pieces of the code individually---ie if the API is broken, your PersonService test won't break in addition to the related API test.

What you want to build to make sure everything works together properly is integration tests.

like image 38
Jonn Avatar answered Oct 22 '22 03:10

Jonn