0
votes

*Update - see end of post*

I have a controller that depends on a service. the service encapsulates $resource. Here is the code

app.controller('charactersController', function ($scope, $routeParams, $location, marvelRepository) {
    var page = parseInt($routeParams.pageNumber);

    marvelRepository.fetch(page - 1).then(function (data) {
        $scope.characters = data.results;
        $scope.paging = {
            page: page,
            total: data.total,
            pageSize: data.limit
        };
    });

    $scope.goto = function (pageNumber) {
        $location.path('/characters/' + pageNumber);
    };
});

marvel.service('marvelRepository', function (hash, $resource) {
    var extended = ng.extend(hash.authenticate(), { id: '@id' });
    var characters = $resource('http://gateway.marvel.com/v1/public/characters/:id', extended);

    return {
        get: function (characterId) {
            return characters
                .get({ id: characterId })
                .$promise
                .then(function (response) {
                    if (response.code !== 200) {
                        throw response;
                    }

                    return response.data.results[0];
                });
        },
        fetch: function (pageIndex) {
            return characters
                .get({ limit: 20, offset: pageIndex * 20 })
                .$promise
                .then(function (response) {
                    if (response.code !== 200) {
                        throw response;
                    }

                    return response.data;
                });
        }
    };
});

the hash dependency creates authentication parameters for the marvel api: public key, timestamp & hash value.

The website work and I can display a list of characters. Now I'm trying to write tests (because angular promotes easy testablity....)

Here is my test

describe("Angular Demo - Test", function () {
    beforeEach(angular.mock.module('demo'));

    describe('requiring Marvel respository', function() {
        var url = 'http://gateway.marvel.com/v1/public/characters?apikey=123&hash=abc123&limit=20&offset=0&ts=20140601073322';
        var httpBackend, location, scope, controller;

        beforeEach(module(function (hashProvider) {
            hashProvider.override({
                apikey: '123',
                ts: '20140601073322',
                hash: 'abc123'
            });
        }));

        beforeEach(inject(function ($httpBackend, $location, $rootScope, $controller) {
            controller = $controller;
            location = $location;
            scope = $rootScope.$new();
            httpBackend = $httpBackend;
        }));

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

        describe('charactersController', function () {
            var characters;

            beforeEach(function() {
                characters = new Array(20);

                httpBackend.when('get', url).respond({ data: { results: characters, offset: 2, limit: 20, total: 100 } });

            });

            it('should be able to get a page of characters', function () {
                httpBackend.expect('get', url);

                controller('charactersController', { $scope: scope, $routeParams: { pageNumber: 1 } });

                httpBackend.flush();

                expect(scope.characters).toBe(characters);
            });

            it('should be able to configure paging', function () {
                 httpBackend.expect('get', url);

                controller('charactersController', { $scope: scope, $routeParams: { pageNumber: 1 } });

                httpBackend.flush();

                expect(scope.paging.total).toBe(100);
                expect(scope.paging.page).toBe(1);
                expect(scope.paging.pageSize).toBe(20);
            });

            it('should be able to navigate to another page of characters', function () {
                controller('charactersController', { $scope: scope });

                scope.goto(5);

                expect(location.path()).toBe('/characters/5');
            });
        });
    });
});

when I execute the tests I get the following results

Error: Unexpected request: GET http://gateway.marvel.com/v1/public/characters/1?apikey=123&hash=abc123&ts=20140601073322

Expected get http://gateway.marvel.com/v1/public/characters/1?apikey=123&hash=abc123&ts=20140601073322

Error: Unexpected request: GET http://gateway.marvel.com/v1/public/characters/1?apikey=123&hash=abc123&ts=20140601073322

Expected get http://gateway.marvel.com/v1/public/characters/1?apikey=123&hash=abc123&ts=20140601073322 at $httpBackend (/tests/angular-mock.js:1172:13) at sendReq (/js/angular/angular.js:8286:9) at $http.serverRequest (/js/angular/angular.js:8027:16) at wrappedCallback (/js/angular/angular.js:11574:81) at wrappedCallback (/js/angular/angular.js:11574:81) at /js/angular/angular.js:11660:26 at Scope.$eval (/js/angular/angular.js:12724:28) at Scope.$digest (/js/angular/angular.js:12536:31) at Function.$httpBackend.flush (/tests/angular-mock.js:1447:20) at Object. (/tests/tests.js:113:29)

if I add console statements throughout my code. i can see that marvelRepository.fetch(1) is getting called. however .$promise.then() doesn't appear to execute.

And what doesn't make sense to me is the 1st two lines of the error. First it states an unexpected request was made and then it says an expected call to the url was made. and it's the same url.

any ideas on what I'm overlooking?

*Update* I updated the structure of the test. setting up both when and expect calls.

I also found if I change my backend calls from when('get', ... to when('GET', ... i receive the following test error

undefined: undefined

No stacktrace or line numbers, but undefined. Reviewing the original error it states

Error: Unexpected request: GET url....
Expected get url...

so I'm wondering if it's failing because of the case sensitivity comparing the request method. However, I think it's a timing issue. The service is injected and the method is called, however the promise.then(cb) doesn't execute. But maybe it's not firing because the request methods don't match so $httpbackend doesn't return?

2

2 Answers

0
votes

In a BeforeEach you need to use .when() instead of .expect().

httpBackend
.when('GET', 'http://gateway.marvel.com/v1/public/characters?apikey=123&hash=abc123&limit=20&offset=0&ts=20140601073322')
.respond({ data: { results: characters, offset: 2, limit: 20, total: 100 } });

Using .expect() is typically done in an assertion, not setup.

0
votes

Finally found the solution. The issue is the use of promises and deferred execution. I need to call $scope.apply() as well and '$httpBackend.flush()'

here is a passing test

describe('characterController', function () {
    var characterUrl = 'http://gateway.marvel.com/v1/public/characters/1';
    var character;

    beforeEach(function () {
        character = { name: 'Wolverine'};

        httpBackend.whenGET(characterUrl).respond({ data: { results: [character] } });
    });

    it('should be able to get character with id of 1', function () {
        controller('characterController', { $scope: scope, $routeParams: { id: 1 } });

        scope.$apply();//execute promises
        httpBackend.flush(); //process http requests

        expect(scope.character).toEqual(character);
    });
});