0

I started writing tests for an existing application and encountered two problems.

Here is service method I'm trying to cover:

    function getClinic(id) {
        return $http
            .get("api/clinic/" + id)
            .then(function (resp) {
                return resp.data;
            })
    }

With

 it('should test getClinic method http call', function() {
  $httpBackend
    .expectGET("api/clinic/" + clinicId)
    .respond(200, $q.when(successResponse));

  clinicManager.getClinic(clinicId)
    .then(function(res) {
      httpOutput = res;
    });

  $httpBackend.flush();

  expect(clinicManager.getClinic).toHaveBeenCalledWith(clinicId);
  expect(httpOutput).toEqual(successResponse);
});

But I got the following error

Error: Unexpected request: GET /api/users/current

Indeed, I do have the following route called on app load

angular
    .module('module', [...])
    .config(...) 
    .run(function (userManager) {
        userManager.setCurrentUser();
        // I put this logic here to fetch currently logged user from back-end on every app load
    })

After removing userManager.setCurrentUser(); I got another error

Error: Unexpected request: GET /dashboard

So the /dashboard is initial page which is specified in $routeProvider

 function Routes($routeProvider) {
    $routeProvider
      .when('/', {
        templateUrl: '/dashboard',
        controller: 'dashboard.ctrl',
      })
      .when('/dashboard', {
        templateUrl: '/dashboard',
        controller: 'dashboard.ctrl',
      })
      //... other routes
     .otherwise({
       redirectTo: '/dashboard',
     });

So my question is how can I avoid getting those two errors without putting http expectations into every single service test kit?

Majesty
  • 2,097
  • 5
  • 24
  • 55

2 Answers2

3

The $httpBackend.flush(); is the bad guy here. It will trigger a broadcast which the $routeProvider will intercept and act on. One solution is to mock that call with a return false

$httpBackend.when('GET', '/dashboard').respond(false);
Marcus Höglund
  • 16,172
  • 11
  • 47
  • 69
  • I just do not want to copy this particular piece of code to every test – Majesty Mar 01 '18 at 18:34
  • @LuninRoman no that would suck. but if u look at this from a bigger picture, you have created a dependency on the $http service in one or more locations in your app. By doing that you are locked with it and its implementation. If you instead would create your own service, lets call it httpSercive, which would expose methods to do http. In that service you inject the $http and in all other places in your app where something with http is needed you inject your own httpService. By doing that you can easily switch provider or get around bad implementations like in this case you are facing now. – Marcus Höglund Mar 01 '18 at 20:57
  • So the concept is to mock `httpService` instead of mocking `$http` with angular-mock and firing `$httpBackend.flush` method, am I right? – Majesty Mar 02 '18 at 06:49
  • @LuninRoman yes, that would be my suggestion. Then, you have all the control of the http implemenation and also only have one service which have the dependency on $http – Marcus Höglund Mar 02 '18 at 07:13
  • However, it does not work. In order to test promise.then I have to call $scope.$digest which is also triggers the routes I mentioned – Majesty Mar 02 '18 at 09:56
  • @LuninRoman u need to set up the test correctly. Mock the httpService and returen a fake promise. You don’t need to call digest to verify the call to the httpService – Marcus Höglund Mar 02 '18 at 10:54
0

The problem in my case was that I had one module for the whole application and now I understood that this is completely wrong. I split my app into different modules, and when I writing test for specific component there is no need to upload the whole application, only module the component belongs to. I removed application .run and routeProvider configurations into independent module therefore no need to upload the new config module when I'm testing nested components.

Majesty
  • 2,097
  • 5
  • 24
  • 55