如何在控制器内测试服务响应

时间:2016-02-26 09:55:12

标签: angularjs unit-testing jasmine karma-jasmine angularjs-controller

我正在尝试测试在控制器中执行的服务http调用的响应:

控制器:

define(['module'], function (module) {
    'use strict';

    var MyController = function ($scope, MyService) {

        var vm = this;

        $scope.testScope = 'karma is working!';

        MyService.getData().then(function (data) {
            $scope.result = data.hour
            vm.banner = {
                'greeting': data.greeting
            }
        });
    };    

    module.exports = ['$scope', 'MyService', MyController ];
});

单元测试:

define(['require', 'angular-mocks'], function (require) {
'use strict';

var angular = require('angular');

describe("<- MyController Spec ->", function () {    

    var controller, scope, myService, serviceResponse;

    serviceResponse= {
        greeting: 'hello',
        hour: '12'
    };

    beforeEach(angular.mock.module('myApp'));

    beforeEach(inject(function (_$controller_, _$rootScope_, _MyService_, $q) {
        scope = _$rootScope_.$new();
        var deferred = $q.defer();
        deferred.resolve(serviceResponse);

        myService = _MyService_;
        spyOn(myService, 'getData').and.returnValue(deferred.promise);

        controller = _$controller_('MyController', {$scope: scope});  
        scope.$apply();
    }));

    it('should verify that the controller exists ', function() {
        expect(controller).toBeDefined();
    });    

    it('should have testScope scope equaling *karma is working*', function() {
        expect(scope.testScope ).toEqual('karma is working!');
    });
});
});

如何测试http请求的执行情况,并返回与serviceResponse$scope.result绑定的vm.banner greeting

我试过了:

define(['require', 'angular-mocks'], function (require) {
'use strict';

var angular = require('angular');

describe("<- MyController Spec ->", function () {    

    var controller, scope, myService, serviceResponse, $httpBackend;

    serviceResponse= {
        greeting: 'hello',
        hour: '12'
    };

    beforeEach(angular.mock.module('myApp'));

    beforeEach(inject(function (_$controller_, _$rootScope_, _MyService_, _$httpBackend_) {
        scope = _$rootScope_.$new();
        $httpBackend = _$httpBackend_

        $httpBackend.expectGET("/my/endpoint/here").respond(serviceResponse);

        myService = _MyService_;
        spyOn(myService, 'getData').and.callThrough();

        controller = _$controller_('MyController', {$scope: scope});  
        scope.$apply();
    }));

    it('should call my service and populate scope.result ', function() {
        myService.getData();
        expect(scope.result ).toEqual(serviceResponse.hour);
    });

    it('should verify that the controller exists ', function() {
        expect(controller).toBeDefined();
    });    

    it('should have testScope scope equaling *karma is working*', function() {
        expect(scope.testScope ).toEqual('karma is working!');
    });
});
});

错误:

[should call my service and populate scope.result -----     Expected undefined to be defined.

3 个答案:

答案 0 :(得分:3)

我认为问题在于您的服务会返回一个承诺,所以当您执行此操作时it

it('should call my service and populate scope.result ', function() {
    myService.getData();
    expect(scope.result ).toEqual(serviceResponse.hour);
});

您的服务在到达expect之前可能尚未得到解决,因此您必须先等待承诺的then并在内部进行预期。 你可以做的是分配给$ scope.result的承诺

var MyController = function ($scope, MyService) {

    var vm = this;

    $scope.testScope = 'karma is working!';

    $scope.result = MyService.getData().then(function (data) {
        $scope.result = data.hour
        vm.banner = {
            'greeting': data.greeting
        }
    });
};    

然后在你的测试中,你可以做类似

的事情
it('should call my service and populate scope.result ', function() {
    //myService.getData(); <- you don't have to call this
    scope.result.then(function(){
       expect(scope.result).toEqual(serviceResponse.hour);
    });

});

您需要模拟$httpBackend并期望某些请求并提供模拟响应数据。这里是角docs

的片段
beforeEach(inject(function($injector) {
     // Set up the mock http service responses
     $httpBackend = $injector.get('$httpBackend');

     // backend definition common for all tests    
     $httpBackend.when('GET', '/auth.py')
                            .respond({userId: 'userX'}, {'A-Token': 'xxx'});

   }));

现在,每当$ http调用get /auth.py时,它都会回复模拟数据{userId: 'userX'}, {'A-Token': 'xxx'}

答案 1 :(得分:2)

您不应该在控制器中测试服务的行为。在单独的测试中测试控制器和服务。

控制器测试

在您的控制器中,您不应该关心来自服务的数据来自何处,您应该测试返回的结果是否按预期使用。验证控制器是否在调用时调用所需的方法,并确保$scope.resultvm.banner的值符合您的预期。

类似的东西:

it('should have called my service and populated scope.result ', function() {
    expect(myService.getData).toHaveBeenCalled();
    expect(scope.result).toEqual(serviceResponse.hour);
    expect(vm.banner).toEqual({greeting:serviceResponse.greeting});
});

服务测试

另一方面,您的服务测试应该知道$http调用并且应该验证响应,因此使用来自@Luie Almeda响应的相同资源,为您的服务编写单独的测试,调用该方法getData()执行模拟的$ http调用并返回所需的结果。

类似的东西:

it('Should return serviceResponse', function () {
    var  serviceResponse= {
      greeting: 'hello',
      hour: '12'
    };
    var myData,

    $httpBackend.whenGET('GET_DATA_URL').respond(200, userviceResponse);

    MyService.getData().then(function (response){
      myData = response
    })

    $httpBackend.flush();

    expect(myData).toBe(serviceResponse);
});

您需要使用GET_DATA_URL调用的正确网址替换MyService.getData()。始终尝试将测试保存在与代码相同的模块中。在服务测试中测试控制器测试中的控制器代码和服务代码。

答案 2 :(得分:2)

我理解的是,在期待结果时,你已经制作了一个Ajax&amp;在承诺得到解决时设置数据值。

基本上,当您在控制器实例化时调用myService.getData()方法时,它会执行$http,它会间接执行$httpBackend ajax并从中返回mockData。但是一旦你拨打电话,你就不会等待电话那个完成电话。然后你打电话给你的assert声明,这会使你的统计失败。

请查看以下代码注释以获取解释

it('should call my service and populate scope.result ', function() {
   //on controller instantiation only we make $http call
   //which will make your current execution state of your code in async state
   //and javascript transfer its execution control over next line which are assert statement
   //then obiviously you will have undefined value.
   //you don't need to call below line here, as below method already called when controller instantiated
   //myService.getData();
   //below line gets called without waiting for dependent ajax to complete.
   expect(scope.result ).toEqual(serviceResponse.hour);
});

所以现在你明白你需要告诉我们的测试代码,我们需要等待ajax调用结束时执行assert语句。因此,您可以使用$httpBackend.flush()方法在这种情况下对您有所帮助。在将控制传递给下一行之前,它将清除所有$httpBackend个队列清除队列。

it('should call my service and populate scope.result ', function() {
   $httpBackend.flush();// will make sure above ajax code has received response
   expect(scope.result ).toEqual(serviceResponse.hour);
});