Test a controller with success() and error ()

后端 未结 3 1129
不知归路
不知归路 2021-01-31 10:07

I\'m trying to work out the best way to unit test success and error callbacks in controllers. I am able to mock out service methods, as long as the controller only uses the defa

3条回答
  •  心在旅途
    2021-01-31 10:38

    As someone had mentioned in a deleted answer, success and error are syntactic sugar added by $http so they aren't there when you create your own promise. You have two options:

    1 - Don't mock the service and use $httpBackend to setup expectations and flush

    The idea is to let your myService act like it normally would without knowing it's being tested. $httpBackend will let you set up expectations and responses, and flush them so you can complete your tests synchronously. $http won't be any wiser and the promise it returns will look and function like a real one. This option is good if you have simple tests with few HTTP expectations.

    'use strict';
    
    describe('SimpleControllerTests', function () {
    
        var scope;
        var expectedResponse = { name: 'this is a mocked response' };
        var $httpBackend, $controller;
    
        beforeEach(module('myApp'));
    
        beforeEach(inject(function(_$rootScope_, _$controller_, _$httpBackend_){ 
            // the underscores are a convention ng understands, just helps us differentiate parameters from variables
            $controller = _$controller_;
            $httpBackend = _$httpBackend_;
            scope = _$rootScope_;
        }));
    
        // makes sure all expected requests are made by the time the test ends
        afterEach(function() {
          $httpBackend.verifyNoOutstandingExpectation();
          $httpBackend.verifyNoOutstandingRequest();
        });
    
        describe('should load data successfully', function() {
    
            beforeEach(function() {
               $httpBackend.expectGET('/api/1').response(expectedResponse);
               $controller('SimpleController', { $scope: scope });
    
               // causes the http requests which will be issued by myService to be completed synchronously, and thus will process the fake response we defined above with the expectGET
               $httpBackend.flush();
            });
    
            it('using loadData()', function() {
              scope.loadData();
              expect(scope.data).toEqual(expectedResponse);
            });
    
            it('using loadData2()', function () {
              scope.loadData2();
              expect(scope.data).toEqual(expectedResponse);
            });
        });
    
        describe('should fail to load data', function() {
            beforeEach(function() {
               $httpBackend.expectGET('/api/1').response(500); // return 500 - Server Error
               $controller('SimpleController', { $scope: scope });
               $httpBackend.flush();
            });
    
            it('using loadData()', function() {
              scope.loadData();
              expect(scope.error).toEqual('ERROR');
            });
    
            it('using loadData2()', function () {
              scope.loadData2();
              expect(scope.error).toEqual('ERROR');
            });
        });           
    });
    

    2 - Return a fully-mocked promise

    If the thing you're testing has complicated dependencies and all the set-up is a headache, you may still want to mock the services and the calls themselves as you have attempted. The difference is that you'll want to fully mock promise. The downside of this can be creating all the possible mock promises, however you could make that easier by creating your own function for creating these objects.

    The reason this works is because we pretend that it resolves by invoking the handlers provided by success, error, or then immediately, causing it to complete synchronously.

    'use strict';
    
    describe('SimpleControllerTests', function () {
    
        var scope;
        var expectedResponse = { name: 'this is a mocked response' };
        var $controller, _mockMyService, _mockPromise = null;
    
        beforeEach(module('myApp'));
    
        beforeEach(inject(function(_$rootScope_, _$controller_){ 
            $controller = _$controller_;
            scope = _$rootScope_;
    
            _mockMyService = {
                get: function() {
                   return _mockPromise;
                }
            };
        }));
    
        describe('should load data successfully', function() {
    
            beforeEach(function() {
    
              _mockPromise = {
                 then: function(successFn) {
                   successFn(expectedResponse);
                 },
                 success: function(fn) {
                   fn(expectedResponse);
                 }
              };
    
               $controller('SimpleController', { $scope: scope, myService: _mockMyService });
            });
    
            it('using loadData()', function() {
              scope.loadData();
              expect(scope.data).toEqual(expectedResponse);
            });
    
            it('using loadData2()', function () {
              scope.loadData2();
              expect(scope.data).toEqual(expectedResponse);
            });
        });
    
        describe('should fail to load data', function() {
            beforeEach(function() {
              _mockPromise = {
                then: function(successFn, errorFn) {
                  errorFn();
                },
                error: function(fn) {
                  fn();
                }
              };
    
              $controller('SimpleController', { $scope: scope, myService: _mockMyService });
            });
    
            it('using loadData()', function() {
              scope.loadData();
              expect(scope.error).toEqual("ERROR");
            });
    
            it('using loadData2()', function () {
              scope.loadData2();
              expect(scope.error).toEqual("ERROR");
            });
        });           
    });
    

    I rarely go for option 2, even in big applications.

    For what it's worth, your loadData and loadData2 http handlers have an error. They reference response.data but the handlers will be called with the parsed response data directly, not the response object (so it should be data instead of response.data).

提交回复
热议问题