Javascript 角度单元测试:模拟多个独立的承诺

Javascript 角度单元测试:模拟多个独立的承诺,javascript,angularjs,unit-testing,jasmine,promise,Javascript,Angularjs,Unit Testing,Jasmine,Promise,这是一个很长的问题,所以我首先要问一个我很难回答的问题: 我如何解析在单元测试中使用不同参数运行的相同函数的独立承诺,并获得不同的值? 我很难模拟这样一个环境,其中执行多个http请求,彼此独立,但使用相同的服务对象。 它可以在实际应用中工作,但是为单元测试(Jasmine,Karma)建立一个合适的模拟环境已经被证明是相当困难的 让我解释一下环境,以及我试图: 首先,我有一个Angular控制器,它通过一个自定义服务对象发出一个http请求,并在测试中对此进行模拟。然后,我制作了一个控制器,它

这是一个很长的问题,所以我首先要问一个我很难回答的问题:

我如何解析在单元测试中使用不同参数运行的相同函数的独立承诺,并获得不同的值?

我很难模拟这样一个环境,其中执行多个http请求,彼此独立,但使用相同的服务对象。 它可以在实际应用中工作,但是为单元测试(Jasmine,Karma)建立一个合适的模拟环境已经被证明是相当困难的

让我解释一下环境,以及我试图:

首先,我有一个Angular控制器,它通过一个自定义服务对象发出一个http请求,并在测试中对此进行模拟。然后,我制作了一个控制器,它使用同一个服务对象发出多个独立的http请求,并且我尝试扩展我的单元测试,以涵盖这一个,因为我成功地使用了另一个控制器

关于它如何在具有单个请求/承诺的控制器中工作的背景: 如果你不想经历所有这些,你可以直接跳到真正的问题:测试多个独立的请求和承诺。也许你应该

让我们先用单请求控制器及其工作测试,有一个基础。 SingleRequestController

function OpenDataController($scope, myHttpService) {

    $scope.parameterData = {requestString : "A"};
    $scope.executeSingleRequest = function() {
        myHttpService.getServiceData($scope.parameterData)
            .then(function (response) {
                $scope.result = response.data;
            });
    }

    // Assume other methods, that calls on $scope.executeSingleRequest, $scope.parameterData may also change
}
function OpenDataController($scope, myHttpService) {

    $scope.resultA = "";
    $scope.resultB = "";
    $scope.resultC = "";
    $scope.resultD = "";

    $scope.executeRequest = function(parameterData) {
        myHttpService.getServiceData(parameterData)
            .then(function (response) {
                assignToResultBasedOnType(response, parameterData.requestType);
            });
    }

    $scope.executeMultipleRequestsWithStaticParameters = function(){
        $scope.executeRequest({requestType: "A"});
        $scope.executeRequest({requestType: "B"});
        $scope.executeRequest({requestType: "C"});
        $scope.executeRequest({requestType: "D"});
    };

    function assignToResultBasedOnType(response, type){
        // Assign to response.data to 
        // $scope.resultA, $scope.resultB, 
        // $scope.resultC, or $scope.resultD, 
        // based upon value from type

        // response.data and type should differ,
        // based upon parameter "requestType" in each request
        ...........
    };

    // Assume other methods that may call upon $scope.executeMultipleRequestsWithStaticParameters or $scope.executeRequest
}
正如您可能所想,myHttpService是一个自定义服务,它向设置的URL发送http请求,并添加控制器传递的参数

SingleRequestControllerTest

describe('SingleRequestController', function() {

    var scope, controller, myHttpServiceMock, q, spy;

    beforeEach(module('OppgaveregisteretWebApp'));

    beforeEach(inject(function ($controller, $q, $rootScope, myHttpService) {

        rootScope = $rootScope;
        scope = rootScope.$new();
        q = $q;

        spy = spyOn(myHttpService, 'getServiceData');

        // Following are uncommented if request is executed at intialization
        //myHttpServiceMock= q.defer();
        //spy.and.returnValue(myHttpServiceMock.promise);

        controller = $controller('OpenDataController', {
            $scope: scope,
            httpService: httpService
        });

        // Following are uncommented if request is executed at intialization
        //myHttpServiceMock.resolve({data : "This is a fake response"});
        //scope.$digest();

    }));

    describe('executeSingleRequest()', function () {

        it('should update scope.result after running the service and receive response', function () {

            // Setup example
            scope.parameterdata = {requestString : "A", requestInteger : 64};

            // Prepare mocked promises.
            myHttpServiceMock= q.defer();
            spy.and.returnValue(myHttpServiceMock.promise);

            // Execute method
            scope.executeSingleRequest();

            // Resolve mocked promises
            myHttpServiceMock.resolve({data : "This is a fake response"});
            scope.$digest();

            // Check values
            expect(scope.result).toBe("This is a fake response");
        }); 
    });
});
describe('MultipleRequestsController', function() {

    var scope, controller, myHttpServiceMock, q, spy;

    var lastRequestTypeParameter = [];

    beforeEach(module('OppgaveregisteretWebApp'));

    beforeEach(inject(function ($controller, $q, $rootScope, myHttpService) {

        rootScope = $rootScope;
        scope = rootScope.$new();
        q = $q;

        spy = spyOn(myHttpService, 'getServiceData');

        controller = $controller('OpenDataController', {
            $scope: scope,
            httpService: httpService
        });

    }));

    describe('executeMultipleRequestsWithStaticParameters ()', function () {

        it('should update scope.result after running the service and receive response', function () {

            // Prepare mocked promises.
            myHttpServiceMock= q.defer();
            spy.and.callFake(function (myParam) {
                lastRequestTypeParameter.unshift(myParam.type);
                return skjemaHttpServiceJsonMock.promise;

            // Execute method
            scope.executeMultipleRequestsWithStaticParameters();

            // Resolve mocked promises
            myHttpServiceMock.resolve(createFakeResponseBasedOnParameter(lastRequestTypeParameter.pop()));
            scope.$digest();

            // Check values
            expect(scope.resultA).toBe("U");
            expect(scope.resultB).toBe("X");
            expect(scope.resultC).toBe("Y");
            expect(scope.resultD).toBe("Z");
        }); 
    });

    function createFakeResponseBasedOnParameter(requestType){
        if (requestType==="A"){return {value:"U"}}
        if (requestType==="B"){return {value:"X"}}
        if (requestType==="C"){return {value:"Y"}}
        if (requestType==="D"){return {value:"Z"}}
    };
});
这是我正在使用的一个现实生活实现的轻量级伪拷贝。我只想说,通过尝试和失败,我发现对于myHttpService.getServiceData的每次调用(通常通过直接调用$scope.executeSingleRequest,或通过其他方法间接调用),必须执行以下操作:

  • myHttpServiceMock必须重新初始化(myHttpServiceMock=q.defer();)
  • 初始化spy以返回模拟的承诺(spy.and.returnValue(myHttpServiceMock.promise);)
  • 执行对服务的调用
  • 解析承诺(myHttpServiceMock.Resolve({data:“这是一个假响应”});)
  • 调用摘要(q.defer();)
到目前为止,它是有效的。 我知道这不是最漂亮的代码,每次必须初始化并解析模拟承诺时,最好在每次测试中使用封装这些承诺的方法。我选择在这里展示这一切是为了演示

真正的问题:测试多个独立的请求和承诺: 现在,让我们假设控制器使用不同的参数对服务执行多个独立的请求。在我的实际应用程序中,类似的控制器就是这样:

多任务控制器

function OpenDataController($scope, myHttpService) {

    $scope.parameterData = {requestString : "A"};
    $scope.executeSingleRequest = function() {
        myHttpService.getServiceData($scope.parameterData)
            .then(function (response) {
                $scope.result = response.data;
            });
    }

    // Assume other methods, that calls on $scope.executeSingleRequest, $scope.parameterData may also change
}
function OpenDataController($scope, myHttpService) {

    $scope.resultA = "";
    $scope.resultB = "";
    $scope.resultC = "";
    $scope.resultD = "";

    $scope.executeRequest = function(parameterData) {
        myHttpService.getServiceData(parameterData)
            .then(function (response) {
                assignToResultBasedOnType(response, parameterData.requestType);
            });
    }

    $scope.executeMultipleRequestsWithStaticParameters = function(){
        $scope.executeRequest({requestType: "A"});
        $scope.executeRequest({requestType: "B"});
        $scope.executeRequest({requestType: "C"});
        $scope.executeRequest({requestType: "D"});
    };

    function assignToResultBasedOnType(response, type){
        // Assign to response.data to 
        // $scope.resultA, $scope.resultB, 
        // $scope.resultC, or $scope.resultD, 
        // based upon value from type

        // response.data and type should differ,
        // based upon parameter "requestType" in each request
        ...........
    };

    // Assume other methods that may call upon $scope.executeMultipleRequestsWithStaticParameters or $scope.executeRequest
}
现在,我意识到“assignToResultBasedOnType”可能不是处理对正确属性的赋值的最佳方式,但这正是我们今天所拥有的

在实际应用中,四个不同的结果属性通常接收相同类型的对象,但内容不同。 现在,我想在测试中模拟这种行为

MultipleRequestControllerTest

describe('SingleRequestController', function() {

    var scope, controller, myHttpServiceMock, q, spy;

    beforeEach(module('OppgaveregisteretWebApp'));

    beforeEach(inject(function ($controller, $q, $rootScope, myHttpService) {

        rootScope = $rootScope;
        scope = rootScope.$new();
        q = $q;

        spy = spyOn(myHttpService, 'getServiceData');

        // Following are uncommented if request is executed at intialization
        //myHttpServiceMock= q.defer();
        //spy.and.returnValue(myHttpServiceMock.promise);

        controller = $controller('OpenDataController', {
            $scope: scope,
            httpService: httpService
        });

        // Following are uncommented if request is executed at intialization
        //myHttpServiceMock.resolve({data : "This is a fake response"});
        //scope.$digest();

    }));

    describe('executeSingleRequest()', function () {

        it('should update scope.result after running the service and receive response', function () {

            // Setup example
            scope.parameterdata = {requestString : "A", requestInteger : 64};

            // Prepare mocked promises.
            myHttpServiceMock= q.defer();
            spy.and.returnValue(myHttpServiceMock.promise);

            // Execute method
            scope.executeSingleRequest();

            // Resolve mocked promises
            myHttpServiceMock.resolve({data : "This is a fake response"});
            scope.$digest();

            // Check values
            expect(scope.result).toBe("This is a fake response");
        }); 
    });
});
describe('MultipleRequestsController', function() {

    var scope, controller, myHttpServiceMock, q, spy;

    var lastRequestTypeParameter = [];

    beforeEach(module('OppgaveregisteretWebApp'));

    beforeEach(inject(function ($controller, $q, $rootScope, myHttpService) {

        rootScope = $rootScope;
        scope = rootScope.$new();
        q = $q;

        spy = spyOn(myHttpService, 'getServiceData');

        controller = $controller('OpenDataController', {
            $scope: scope,
            httpService: httpService
        });

    }));

    describe('executeMultipleRequestsWithStaticParameters ()', function () {

        it('should update scope.result after running the service and receive response', function () {

            // Prepare mocked promises.
            myHttpServiceMock= q.defer();
            spy.and.callFake(function (myParam) {
                lastRequestTypeParameter.unshift(myParam.type);
                return skjemaHttpServiceJsonMock.promise;

            // Execute method
            scope.executeMultipleRequestsWithStaticParameters();

            // Resolve mocked promises
            myHttpServiceMock.resolve(createFakeResponseBasedOnParameter(lastRequestTypeParameter.pop()));
            scope.$digest();

            // Check values
            expect(scope.resultA).toBe("U");
            expect(scope.resultB).toBe("X");
            expect(scope.resultC).toBe("Y");
            expect(scope.resultD).toBe("Z");
        }); 
    });

    function createFakeResponseBasedOnParameter(requestType){
        if (requestType==="A"){return {value:"U"}}
        if (requestType==="B"){return {value:"X"}}
        if (requestType==="C"){return {value:"Y"}}
        if (requestType==="D"){return {value:"Z"}}
    };
});
这是测试中发生的情况(在调试期间发现): spy函数运行四次,并将值推入数组lastRequestTypeParameter,该参数将为[D,C,B,A],假定这些值将弹出以读取A-B-C-D,以反映请求的真实顺序

然而,问题来了:解决只发生一次,并且为所有四个结果属性创建相同的响应:{value:“U”}

在内部选择正确的列表,因为承诺链使用的参数值与服务调用(requestType)中使用的参数值相同,但它们都只在第一次响应时接收数据。因此,结果是:

$scope.resultA=“U”$scope.resultB=“U”,依此类推。。。。而不是U,X,Y,Z

spy函数运行了四次,我假设返回了四个承诺,每次调用一个。但到目前为止,只有一个resolve()和一个q.digest()。 为了让事情顺利进行,我尝试了以下方法:

  • 四问
  • 四项决议
  • 四个摘要
  • 返回一个包含四个不同对象的数组,对应于我在工作测试中所期望的。(愚蠢,我知道,它不同于预期的对象结构,但是当你试图调整任何东西以获得令人惊讶的工作结果时,你不做什么?)
这些都不管用。事实上,第一次解析会对所有四个属性产生相同的结果,因此添加更多的解析和摘要将不会有什么不同

我曾尝试用谷歌搜索这个问题,但我发现的要么是不同服务的多个承诺,要么是多个链函数(.then().then()…),要么是嵌套的异步调用(链中的新承诺对象)

我需要的是一个独立承诺的解决方案,通过使用不同的参数运行相同的函数来创建

因此,我将以我提出的问题作为结束:
我如何解决在单元测试中使用不同参数运行的同一函数的独立承诺,并获得不同的值?Jasmine是所有行业中对角度友好的杰克。它通常适用于大多数前端测试用例。它缺乏间谍/模拟功能,而Sinon提供了更强大的功能

这可能就是为什么摩卡/西农/柴模块化捆绑包在某些时候可能会被首选的原因,但其模块化的好处是西农与捆绑包没有联系。除了与柴胡关系密切外,它还可以与柴胡一起使用

使西农成为比茉莉花间谍更好的选择的原因是它是cap