使用 Jasmine 对包含私有超时的单元测试 Angularjs 指令

Posted

技术标签:

【中文标题】使用 Jasmine 对包含私有超时的单元测试 Angularjs 指令【英文标题】:Unit Test Angularjs directive, which contains private timeout, with Jasmine 【发布时间】:2016-09-28 06:41:47 【问题描述】:

我有一个指令,它的行为应该有所不同,具体取决于自初始化以来经过的时间:

am.directive('showText', () => (
  restrict: 'E',
  replace: true,
  scope: 
    value: '@'
  ,
  controller: ($scope, $timeout) => 
    console.log('timeout triggered');

    $scope.textVisible = false;

    let visibilityCheckTimeout = $timeout(() => 
      if (parseInt($scope.value, 10) < 100) 
        $scope.textVisible = true;
      
    , 330);

    // Clear timeout upon directive destruction
    $scope.$on('$destroy', $timeout.cancel(visibilityCheckTimeout));
  ,
));

问题是,当我尝试使用 Jasmine 对其进行测试时,我似乎无法找到以任何方式触发此超时的方法。已经尝试过$timeout.flush()$timeout.verifyNoPendingTasks()(如果我要评论flush 调用,实际上会引发错误)。但它仍然没有触发超时的回调执行

describe('showText.', () => 
  let $compile;
  let $rootScope;
  let $scope;
  let $timeout;

  const compileElement = (rootScope, value = 0) => 
    $scope = rootScope.$new();
    $scope.value = value;

    const element = $compile(`
      <show-text
        value="value"
      ></show-text>
    `)($scope);

    $scope.$digest();

    return element;
  ;

  beforeEach(() => 
    module('app.directives.showText');

    inject((_$compile_, _$rootScope_, _$timeout_) => 
      $compile = _$compile_;
      $rootScope = _$rootScope_;
      $timeout = _$timeout_;
    );
  );

  it(`Process lasts > 0.33s. Should show text.`, () => 
    const VALUE = 30;
    const element = compileElement($rootScope, VALUE);
    const elementContent = element.find('.show-text__content');

    $timeout.flush(1000);

    $timeout.verifyNoPendingTasks();

    $rootScope.$digest();

    expect(element.isolateScope().textVisible).toBeTruthy();
    expect(elementContent.length).toEqual(1);
    expect(elementContent.text().trim()).toBe('Example text');
  );
);

测试失败。

找不到我做错了什么。有关如何正确测试这种情况的任何提示?

谢谢。

UPD 经过一番调查,我发现在这个特定的测试用例中,compileElement 函数中,value 属性没有被$compile 服务评估。等于"value"。我已经使用了 10 次相同的函数,但无法理解为什么它不像以前那样使用 $scope.value 的属性。

【问题讨论】:

你怎么知道没有运行超时,你用断点检查了吗? @estus 带有断点,以及在其中放置控制台日志。还删除了这个“if”语句并检查属性textVisible 是否更改。通常在将console.logs 放入其中时,它们会在终端的测试用例之间给出输出。目前正在尝试使用 $scope 公开超时的回调,并对其进行监视。 @estus 向问题添加了“更新”。似乎由于某种原因,$compile 服务未评估传递的值。 【参考方案1】:

发生这种情况的原因是$timeout.cancel(visibilityCheckTimeout) 无条件立即启动。相反,它应该是

$scope.$on('$destroy', () => $timeout.cancel(visibilityCheckTimeout));

可以做一些事情来提高可测试性(除了$timeout 在这里作为一次性范围观察者并要求被替换)。

$timeout可以成功spied:

beforeEach(module('app', ($provide) => 
  $provide.decorator('$timeout', ($delegate) => 
    var timeoutSpy = jasmine.createSpy().and.returnValue($delegate);
    angular.extend(timeoutSpy, $delegate);
    spyOn(timeoutSpy, 'cancel').and.callThrough();
    return timeoutSpy;
  );
));

私有的$timeout 回调可以暴露在作用域中。

$scope._visibilityCheckHandler = () => 
  if (parseInt($scope.value, 10) < 100) 
    $scope.textVisible = true;
  
;
$timeout($scope._visibilityCheckHandler, 330);

通过这种方式可以监视所有呼叫并获得全面覆盖:

let directiveScope;
...

const element = $compile(`...`)($scope);

directiveScope = element.isolateScope();
spyOn(directiveScope, '_visibilityCheckHandler').and.callThrough();

$scope.$digest();
...

expect($timeout).toHaveBeenCalledWith(directiveScope._visibilityCheckHandler, 330);
expect($timeout.cancel).not.toHaveBeenCalled();

在这种情况下,不需要为 '>= 0.33s' 和 'flush 延迟参数的规范,$timeout 的内部工作已经在 Angular 规范中进行了测试。此外,回调逻辑可以与$timeout 规范分开测试。

【讨论】:

修改了我测试这些东西的方法,并采取了方法,你建议。谢谢

以上是关于使用 Jasmine 对包含私有超时的单元测试 Angularjs 指令的主要内容,如果未能解决你的问题,请参考以下文章

为啥使用 jasmine 对 Node typescript 项目进行 Karma 单元测试会显示包含依赖项的覆盖范围?

在私有方法上使用 Jasmine spyon

如何对 DOM 操作进行单元测试(使用 jasmine)

使用 jasmine 对控制器中基于资源的工厂进行角度 js 单元测试

Jasmine 使用 Angular 中的 TypeScript 对文件大小进行单元测试

如何在 Jasmine 单元测试中使用 Sinon 对 jQuery 动画进行假时间?