最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

javascript - Use $httpBackend to mock $http calls change the expected url Angular - Stack Overflow

programmeradmin1浏览0评论

I am trying to test a service which is using $http

 var APIClient = function($http) {
     this.send = function(data) {
         $http({
             method: data.method,
             url: data.url,
             headers: data.headers,
             data: data.data
         }).success(function(response, status) {
             data.success(response, status);
         }).error(function(response, status) {
             data.error(response, status);
         });
     }
 }

 angular.module('api.client', []).factory('APIClient', ['$http'
     function($http) {
         var client = new APIClient($http);

         return {
             send: function(data) {
                 return client.send(data);
             },
         }

     }
 ]);

And the test

  describe('send', function() {

      var apiClient, $httpBackend;

      beforeEach(module('compare'));

      beforeEach(inject(function($injector) {
          $httpBackend = $injector.get('$httpBackend');
          apiClient = $injector.get('APIClient');
      }));

      it('Should check if send() exists', function() {
          expect(apiClient.send).toBeDefined();
      });

      it('Should send GET request', function(done) {
          var url = '/';

          $httpBackend.expect('GET', url).respond({});

          apiClient.send({
              url: url,
              success: function(data, status) {
                  console.log(status);
                  done();
              },
              error: function(data, status) {
                  console.log(status);
                  done();
              }
          });

          $httpBackend.flush();
      });
  });

But I always have this error

PhantomJS 1.9.8 (Mac OS X) send Should send GET request FAILED
        Error: Unexpected request: GET templates/test.html
        Expected GET /

The expected url is always the last state in my app.js In this case

// Ionic Starter App

// angular.module is a global place for creating, registering and retrieving Angular modules
// 'starter' is the name of this angular module example (also set in a <body> attribute in index.html)
// the 2nd parameter is an array of 'requires'
// 'starter.services' is found in services.js
// 'starter.controllers' is found in controllers.js

angular.module('compare',
    [
        'ionic',
        'manager.user',
        'api.client',
        'api.user',
        'apipare',
        'user.controllers',
        'test.controllers'
    ]
)

    .run(function ($ionicPlatform) {
        $ionicPlatform.ready(function () {
            // Hide the accessory bar by default (remove this to show the accessory bar above the keyboard
            // for form inputs)
            if (window.cordova && window.cordova.plugins && window.cordova.plugins.Keyboard) {
                cordova.plugins.Keyboard.hideKeyboardAccessoryBar(true);
            }
            if (window.StatusBar) {
                // org.apache.cordova.statusbar required
                StatusBar.styleLightContent();
            }
        });
    })

    .config(function ($stateProvider, $urlRouterProvider) {

        // Ionic uses AngularUI Router which uses the concept of states
        // Learn more here: 
        // Set up the various states which the app can be in.
        // Each state's controller can be found in controllers.js
        $stateProvider

            // setup an abstract state for the tabs directive
            .state('tab', {
                url: "/tab",
                abstract: true,
                templateUrl: "templates/tabs.html"
            })

            // Each tab has its own nav history stack:

            .state('tab.dash', {
                url: '/dash',
                views: {
                    'tab-dash': {
                        templateUrl: 'templates/tab-dash.html',
                        controller: 'DashCtrl'
                    }
                }
            })

            .state('subscription', {
                url: '/subscription',
                templateUrl: 'templates/subscription.html',
                controller: 'SubscriptionCtrl'
            })

            .state('login', {
                url: '/login',
                templateUrl: 'templates/login.html',
                controller: 'LoginCtrl'
            })

            .state('test-compare', {
                url: '/test/compare',
                templateUrl: 'templates/test.html',
                controller: 'TestCompareCtrl'
            })

        // if none of the above states are matched, use this as the fallback
        $urlRouterProvider.otherwise('/login');

    });

I don't understand why the url is changing I am giving / and it test templates/test.html which is always the last state template

I am trying to test a service which is using $http

 var APIClient = function($http) {
     this.send = function(data) {
         $http({
             method: data.method,
             url: data.url,
             headers: data.headers,
             data: data.data
         }).success(function(response, status) {
             data.success(response, status);
         }).error(function(response, status) {
             data.error(response, status);
         });
     }
 }

 angular.module('api.client', []).factory('APIClient', ['$http'
     function($http) {
         var client = new APIClient($http);

         return {
             send: function(data) {
                 return client.send(data);
             },
         }

     }
 ]);

And the test

  describe('send', function() {

      var apiClient, $httpBackend;

      beforeEach(module('compare'));

      beforeEach(inject(function($injector) {
          $httpBackend = $injector.get('$httpBackend');
          apiClient = $injector.get('APIClient');
      }));

      it('Should check if send() exists', function() {
          expect(apiClient.send).toBeDefined();
      });

      it('Should send GET request', function(done) {
          var url = '/';

          $httpBackend.expect('GET', url).respond({});

          apiClient.send({
              url: url,
              success: function(data, status) {
                  console.log(status);
                  done();
              },
              error: function(data, status) {
                  console.log(status);
                  done();
              }
          });

          $httpBackend.flush();
      });
  });

But I always have this error

PhantomJS 1.9.8 (Mac OS X) send Should send GET request FAILED
        Error: Unexpected request: GET templates/test.html
        Expected GET /

The expected url is always the last state in my app.js In this case

// Ionic Starter App

// angular.module is a global place for creating, registering and retrieving Angular modules
// 'starter' is the name of this angular module example (also set in a <body> attribute in index.html)
// the 2nd parameter is an array of 'requires'
// 'starter.services' is found in services.js
// 'starter.controllers' is found in controllers.js

angular.module('compare',
    [
        'ionic',
        'manager.user',
        'api.client',
        'api.user',
        'api.compare',
        'user.controllers',
        'test.controllers'
    ]
)

    .run(function ($ionicPlatform) {
        $ionicPlatform.ready(function () {
            // Hide the accessory bar by default (remove this to show the accessory bar above the keyboard
            // for form inputs)
            if (window.cordova && window.cordova.plugins && window.cordova.plugins.Keyboard) {
                cordova.plugins.Keyboard.hideKeyboardAccessoryBar(true);
            }
            if (window.StatusBar) {
                // org.apache.cordova.statusbar required
                StatusBar.styleLightContent();
            }
        });
    })

    .config(function ($stateProvider, $urlRouterProvider) {

        // Ionic uses AngularUI Router which uses the concept of states
        // Learn more here: https://github.com/angular-ui/ui-router
        // Set up the various states which the app can be in.
        // Each state's controller can be found in controllers.js
        $stateProvider

            // setup an abstract state for the tabs directive
            .state('tab', {
                url: "/tab",
                abstract: true,
                templateUrl: "templates/tabs.html"
            })

            // Each tab has its own nav history stack:

            .state('tab.dash', {
                url: '/dash',
                views: {
                    'tab-dash': {
                        templateUrl: 'templates/tab-dash.html',
                        controller: 'DashCtrl'
                    }
                }
            })

            .state('subscription', {
                url: '/subscription',
                templateUrl: 'templates/subscription.html',
                controller: 'SubscriptionCtrl'
            })

            .state('login', {
                url: '/login',
                templateUrl: 'templates/login.html',
                controller: 'LoginCtrl'
            })

            .state('test-compare', {
                url: '/test/compare',
                templateUrl: 'templates/test.html',
                controller: 'TestCompareCtrl'
            })

        // if none of the above states are matched, use this as the fallback
        $urlRouterProvider.otherwise('/login');

    });

I don't understand why the url is changing I am giving / and it test templates/test.html which is always the last state template

Share Improve this question edited Jul 10, 2015 at 4:40 Bhavesh Jariwala 8758 silver badges27 bronze badges asked Jul 1, 2015 at 9:53 AjouveAjouve 10.1k27 gold badges94 silver badges153 bronze badges 3
  • When you inject $httpBackend in our tests it intercepts all http requests. So obviously you have some code (not from APIClient if this is the only code of it that is presented in OP) that triggers states. Can you provide code that defines compare module? – Kirill Slatin Commented Jul 3, 2015 at 13:20
  • @KirillSlatin I just updated the the last code will the whole app.js – Ajouve Commented Jul 3, 2015 at 13:25
  • Why do you declare APIClient service in api.client module, but call compare in test suite for the service? – Kirill Slatin Commented Jul 3, 2015 at 14:22
Add a comment  | 

4 Answers 4

Reset to default 7 +50

Your main issue here is this line:

beforeEach(module('compare'));

You are loading your entire app here instead of just the apiClient. Essentially, you are doing a full blown integration test, instead of a unit test.

You should be loading only api.client.

beforeEach(module('api.client'));

Something useful to note, you could also do something like:

$httpBackend.whenGET(/templates\/(.*)/).respond(''); which basically ignores all templates that get loaded by routers, controllers, or directives. If you do this though, it still wouldn't be considered a unit test, because you are not strictly testing just your APIClient.

Another useful note:

Anything you execute inside of .run or .config should not be an annonymous function, that way you could mock it.

An example of this would be doing:

.config(CompareStateLoader);

CompareStateLoader.$inject = [
    '$stateProvider', 
    '$urlRouterProvider'
];

function CompareStateLoader(
    $stateProvider, 
    $urlRouterProvider
){
    //configure states here 
}

Doing this would allow you to mock CompareStateLoader and load this in your test runner.

For more information on this, please see John Papa's Angular Style Guide here.

I would suggest to compile all your templates into JS file (for example with grunt "html2js" task or karma preprocessor "ng-html2js") and do not have headache with GETing templates.

Or you also could use passThrough

$httpBackend.when('GET', /\.html$/).passThrough()

Example - http://plnkr.co/edit/pbjcDl?p=preview

But I would suggest to use first option.

var apiClient, $httpBackend, $loc;

    beforeEach(module('compare'));

    beforeEach(inject(function($injector, $location) {
        $httpBackend = $injector.get('$httpBackend');
        apiClient = $injector.get('APIClient');
        $loc = $location;
    }));


it ('Should send GET request', function(done) {
        expect($loc.path()).toEqual('');
        var url = '/';

        $httpBackend.expect('GET', $loc.path('/')).respond({});

        apiClient.send({
            url: url,
            success: function(data, status) {
                console.log(status);
                done();
            },
            error: function(data, status) {
                console.log(status);
                done();
            }
        });

        $httpBackend.flush();
    });

Edit You should use angular-mock to use $location

beforeEach(inject(function(_$httpBackend_, APIClient) {
        $httpBackend = _$httpBackend_;
        apiClient = APIClient;
    }));

Add this line in your before each block - $httpBackend.expect('GET', "templates/test.html").respond(200);

发布评论

评论列表(0)

  1. 暂无评论