使用webworker测试q.defer(),如何防止计时问题?

时间:2016-06-13 13:43:37

标签: angularjs jasmine web-worker deferred

我有一个网络工作者为我做了一些工作。 我把它包装成了一个服务,这个webworker在Promise中执行。

现在我用Jasmine进行测试,看来承诺在测试完成后返回。

这里的难点在于延迟和网络工作者在不同的时间点都是异步的。

我尝试过使用done,setTimeout,$ scope。$ apply()执行async jasmine。但是' deferred.resolve(e.data.filtered)''在所有这些计时器被暂停后调用。

我的角度服务是这样的:

'use strict';

angular.module('app.demographics').service('FilteringService', FilteringService);
FilteringService.$inject = ['$q'];

function FilteringService($q) {
    this.filter = function (dataSet, filters) {
        var deferred = $q.defer();
        var worker = new Worker('my.worker.js');
        var filterData = {
            dataSet: dataSet,
            filters: filters
        };
        worker.postMessage(filterData);
        worker.onmessage = function (e) {
            if (e.data && e.data.ready) {
                deferred.resolve(e.data.filtered);
            }
        };
        worker.onerror = function (e) {
            console.log("something went wrong while filtering: ", e);
            deferred.reject(e);
        };

        return deferred.promise;
    };
}

我的测试就像这样,我期望它能正常工作,但它永远不会达到预期。

'use strict';

describe('FilteringService: ', function () {

    var filteringService, $q,
        dataSet = [{a: 1, b: 2}, {c: 3, d: 4}],
        filters = [];

    beforeEach(function () {
        module('app.demographics');

        inject(function (_$rootScope_, _FilteringService_, _$q_) {
            filteringService = _FilteringService_;
            $q = _$q_;
        });
    });

    it('should return a promise on filtering', function () {
        var filteringPromise = filteringService.filter(dataSet, filters);

        filteringPromise.then(function (data) {
            expect(data.length).toEqual(dataSet.length);
        }, function (failure) {
            fail(failure);
        });
    });
});

3 个答案:

答案 0 :(得分:0)

我接受这不是最佳解决方案,可能更多是黑客攻击,但这就是我让Jasmine与Angular合作的方式。我的方法是创建一个函数 digestIt ,它接受Jasmine提供的 done 函数,并使用setInterval调用$ digest并返回一个清理函数。

function digestIt($rootScope, done) {
var intervalId: number,     
    _done = function() {
        if (angular.isDefined(intervalId))
            clearInterval(intervalId);
        intervalId = null;
        done();
    },
    _interval = function () {
        if (angular.isNumber(intervalId)) {
            try {
                $rootScope.$digest();
            } catch (e) {                
                _done();             
            }
        }
    },
    intervalId = setInterval(_interval, 1);
    return _done;
}

这是使用模式。

describe("MyService ", function() {
    var $rootScope,
        $injector
        ;

    beforeEach(angular.mock.inject(function (_$rootScope_, _$injector_) {
        $rootScope = _$rootScope_.$new();
        $injector = _$injector_;
    }));

    it("My Test", function (done) {
        var $docs = $injector.get('MyService'),
            completed = digestIt($rootScope, done)
            ;
        $docs.asyncCall().then(function () {
            /* expect */
        }).catch(function() {
            /* fail */
        }).finally(function () {
            completed();
        });
    });
});

答案 1 :(得分:0)

看起来喜欢(至少在测试环境中),$q承诺只有在启动摘要周期时才会得到解决(例如,调用它们的成功/失败回调)。因此,在服务中,您可以放入$rootScope.apply()来触发此操作:

worker.onmessage = function (e) {
  if (e.data && e.data.ready) {
    $rootScope.$apply(function() {
      deferred.resolve(e.data.filtered);
    });
  }
};
worker.onerror = function (e) {
  console.log("something went wrong while filtering: ", e);
  $rootScope.$apply(function() {
    deferred.reject(e);
  });
};

然后你的测试可以是异步的:

it('should return a promise on filtering', function (done) {
  var filteringPromise = filteringService.filter(dataSet, filters);

  filteringPromise.then(function (data) {
    expect(data.length).toEqual(dataSet.length);
    done();
  }, function (failure) {
    fail(failure);
  });
});

可以在https://plnkr.co/edit/D21EhoCXIbj8R0P9RY40?p=preview

看到

注意:这可能被归类为集成测试而不是单元测试,因为您同时测试FilteringService和您的工作人员。如果您只进行单元测试,则可以通过模拟工作人员来避免在$rootScope.$apply()中添加FilteringService。您可能也可以进行同步测试。

答案 2 :(得分:0)

https://stackoverflow.com/a/37853075/1319998中所述,原始测试似乎更像是集成测试而不是单元测试。如果您希望这是一个单元测试......

你需要能够嘲笑工人,所以你没有测试它的作用。因此,在服务中,您可以调用Worker,而不是直接调用$window.Worker,因为$window可以在测试中轻松模拟。

app.service('FilteringService', FilteringService);
FilteringService.$inject = ['$window', '$q', '$rootScope'];

function FilteringService($window, $q, $rootScope) {
  this.filter = function (dataSet, filters) {
    var deferred = $q.defer();
    var worker = new $window.Worker('my.worker.js');
    ...

然后在测试中你可以创建一个模拟的worker,调用将由真正的worker调用的att onmessage处理程序,然后测试promise是否以正确的值解析(我已经离开了只是测试长度,但在真正的测试中,我怀疑你需要更好的东西)。

describe('FilteringService: ', function () {

  var $rootScope, filteringService, $q,
    dataSet = [{a: 1, b: 2}, {c: 3, d: 4}],
    filters = [];

  var mockWorker;
  var mockWindow = {
    Worker: function() {
      return mockWorker;
    }
  };

  beforeEach(function () {
    module('app.demographics');

    module(function($provide) {
      $provide.value('$window', mockWindow);
    });

    inject(function (_$rootScope_, _FilteringService_, _$q_) {
      $rootScope = _$rootScope_;
      filteringService = _FilteringService_;
      $q = _$q_;
    });

    mockWorker = {
      postMessage: jasmine.createSpy('onMessage')
    }
  });

  it('when onmessage from worker called, resolves returned promise with filtered list', function () {
    expect(mockWorker.postMessage).not.toHaveBeenCalled();
    expect(mockWorker.onmessage).not.toEqual(jasmine.any(Function));

    var filteringPromise = filteringService.filter(dataSet, filters);

    expect(mockWorker.postMessage).toHaveBeenCalled();
    expect(mockWorker.onmessage).toEqual(jasmine.any(Function));

    mockWorker.onmessage({
      data: {
        ready: true,
        filtered: dataSet
      }
    });

    var result;
    filteringPromise.then(function(_result) {
      result = _result;
    });
    $rootScope.$apply();
    expect(result.length).toEqual(dataSet.length);
  });
});

请注意,您需要测试中的$apply(而不是服务),以确保调用promise回调。

您可以在https://plnkr.co/edit/g2q3ZnD8AGZCkgkkEkdj?p=preview

看到这种情况