Search code examples
javascriptangularjsunit-testingangularjs-ngmock

ngMock complains unexpected request when request is expected


My ng app is working fine, but I am trying to write a ngMock test for my controller; I am basically following along the example on angular's website: https://docs.angularjs.org/api/ngMock/service/$httpBackend

The problem I am running into is that it complains about unexpected request even when request is being expected.

PhantomJS 1.9.8 (Windows 8 0.0.0) NotificationsController should fetch notification list FAILED

Error: Unexpected request: GET Not valid for testsapi/AspNetController/AspNetAction Expected GET api/AspNetController/AspNetAction

What I do not get is that, on the error line, why is there a "tests" word appended before my service url? I thought it should be sending to 'api/AspNetController/AspNetAction' What am I doing wrong here. I can't find any one else running into the same problem as me through google.

Edit: I noticed that, if i remove the sendRequest portion from my controller, and have the unit test log my request object in console, i see the following json.

{  
   "method":"GET",
   "url":"Not valid for testsapi/AspNetController/AspNetAction",
   "headers":{  
      "Content-Type":"application/json"
   }
}

here is the controller code

angular.module('MainModule')
    .controller('NotificationsController', ['$scope', '$location', '$timeout', 'dataService',
        function ($scope, $location, $timeout, dataService) {
            //createRequest returns a request object
            var fetchNotificationsRequest = dataService.createRequest('GET', 'api/AspNetController/AspNetAction', null);
            //sendRequest sends the request object using $http
            var fetchNotificationsPromise = dataService.sendRequest(fetchNotificationsRequest);
            fetchNotificationsPromise.then(function (data) {
                //do something with data.
            }, function (error) {
                alert("Unable to fetch notifications.");
            });
    }]
);

Test code

describe('NotificationsController', function () {
    beforeEach(module('MainModule'));
    beforeEach(module('DataModule')); //for data service

    var $httpBackend, $scope, $location, $timeout, dataService;

    beforeEach(inject(function ($injector) {

        $httpBackend = $injector.get('$httpBackend');

        $scope = $injector.get('$rootScope');
        $location = $injector.get('$location');
        $timeout = $injector.get('$timeout');
        dataService = $injector.get('dataService');

        var $controller = $injector.get('$controller');

        createController = function () {
            return $controller('NotificationsController', {
                '$scope': $scope,
                '$location': $location,
                '$timeout': $timeout,
                'dataService': dataService,
            });
        };
    }));

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

    it('should fetch notification list', function () {
        $httpBackend.expectGET('api/AspNetController/AspNetAction');        //this is where things go wrong
        var controller = createController();

        $httpBackend.flush();
    });

});

Data service code

    service.createRequest = function(method, service, data) {
        var req = {
            method: method, //GET or POST
            url: someInjectedConstant.baseUrl + service,
            headers: {
                'Content-Type': 'application/json'
            }
        }

        if (data != null) {
            req.data = data;
        }

        return req;
    }

    service.sendRequest = function (req) {
        return $q(function (resolve, reject) {
            $http(req).then(function successCallback(response) {
                console.info("Incoming response: " + req.url);
                console.info("Status: " + response.status);
                console.info(JSON.stringify(response));

                if (response.status >= 200 && response.status < 300) {
                    resolve(response.data);
                } else {
                    reject(response);
                }
            }, function failCallback(response) {
                console.info("Incoming response: " + req.url);
                console.info("Error Status: " + response.status);
                console.info(JSON.stringify(response));

                reject(response);
            });
        });
    }

ANSWER:

since dataService created the finalized webapi url by someInjectedConstant.baseUrl + whatever_relative_url passed in from controller, In the test that I am writting, I will have to inject someInjectedConstant and

$httpBackend.expectGET(someInjectedConstant.baseUrl + relativeUrl)

instead of just doing a $httpBackend.expectGET(relativeUrl)


Solution

  • Clearly Not valid for tests is getting prepended to your url somewhere in your code. It's also not adding the hardcoded domain (see note below). Check through all your code and any other parts of the test pipeline that might be adding this to the url.

    A couple of points on your code:

    • avoid hardcoding domain names in your code (I see you've fixed this in your updated answer)
    • maybe someInjectedConstant could be more explicitly named
    • there is no need for you to wrap $http with $q, so service.sendRequest can be:

      service.sendRequest = function (req) {
          $http(req).then(function (response) { // no need to name the function unless you want to call another function with all success/error code in defined elsewhere
              console.info("Incoming response: " + req.url);
              console.info("Status: " + response.status);
              console.info(JSON.stringify(response));
              return response.data; // angular treats only 2xx codes as success
          }, function(error) {
              console.info("Incoming response: " + req.url);
              console.info("Error Status: " + response.status);
              console.info(JSON.stringify(response));
          });
      }