Как условно сбросить $timeout в модульном тесте Angular

У меня есть код, который выглядит примерно так:

function doThing() {
  if (invalidInput) {
    console.error('Invalid input.');
    return;
  }

  $timeout(function() {
    MyService.doThing();
  }, 1000);
}

Я хочу проверить это MyService.doThing не вызывается при вводе неверного ввода.

Если я позвоню doThing(invalidInput) без выполнения $timeout.flush(), MyService.doThing не будет вызван, независимо от того, есть ли у меня строки 2-5. Таким образом, чтобы действительно проверить, MyService.doThing вызывается, когда вводится неверный ввод, мне нужно позвонить $timeout.flush,

Проблема в том, что выдает ошибку, если я пытаюсь сбросить, когда нечего сбросить. Error: No deferred tasks to be flushed,

Как я могу справиться с этим сценарием? Я хотел бы сделать что-то вроде, $timeout.flushIfFlushable(),

2 ответа

Никакая неопределенность не приветствуется в модульных тестах, должно быть предсказуемо, есть ли что-то для $timeout или нет.

В этом фрагменте кода должны быть проверены два случая, в обоих $timeout не должен быть черным ящиком, вместо этого он должен быть заглушкой (по желанию, шпионом, который оборачивается вокруг реальной службы).

beforeEach(module('app', ($provide) => {
  $provide.decorator('$timeout', ($delegate) => {
    var timeoutSpy = jasmine.createSpy().and.returnValue($delegate);
    // methods aren't copied automatically to spy
    return angular.extend(timeoutSpy, $delegate);
  });
}));

Первый фальси invalidInput:

...
MyService.doThing();
expect($timeout).not.toHaveBeenCalled();

А второй правдивый invalidInput:

...
MyService.doThing();
expect($timeout).toHaveBeenCalledWith(jasmine.any(Function), 1000);
$timeout.flush();
expect(MyService.doThing).toHaveBeenCalledTimes(2);

Независимо от этого случая, как правило, полезно возвращать обещания из функций, основанных на обещаниях:

function doThing() {
  if (invalidInput) {
    console.error('Invalid input.');
    return;
  }

  return $timeout(function() {
    return MyService.doThing();
  }, 1000);
}

Таким образом, вызывающие (наиболее вероятные спецификации) могут иметь некоторый контроль над асинхронным поведением функции.


Отвечая на вопрос напрямую, ожидаемый способ сделать flushIfFlushable()

try {
  $timeout.verifyNoPendingTasks(); // just for semantics, not really necessary
  $timeout.flush();
} catch (e) {}

Чего следует избегать по причинам, перечисленным выше.

Я предлагаю определить два отдельных модульных теста, чтобы проверить поведение вашего doThing функция. Попробуйте следующий код:

  • контроллер
(function () {
  'use strict';

  angular.module('myApp', [])
    .controller('MainCtrl', function ($timeout, MyService) {
      var vm = this;

      vm.invalidInput = true;
      vm.doThing = doThing;

      function doThing() {
        if (vm.invalidInput) {
          return;
        }

        $timeout(function () {
          MyService.doThing();
        }, 1000);
      }

    });
})();
  • обслуживание
(function () {
  'use strict';

  angular.module('myApp').service('MyService', MyService);

  function MyService() {

    this.doThing = function () {
      // doThing code
    };
  }
})();
  • Модульный тест
'use strict';

describe('Controller: MainCtrl', function () {

  beforeEach(module('myApp'));

  var vm,
    $timeout,
    MyService;

  beforeEach(inject(function (_$controller_, _$timeout_, _MyService_) {
    $timeout = _$timeout_;
    MyService = _MyService_;
    vm = _$controller_('MainCtrl', {
      $timeout: $timeout,
      MyService: MyService
    });
  }));

  it('should call doThing for valid inputs', function () {
    spyOn(MyService, 'doThing').andCallThrough();

    vm.invalidInput = false;
    vm.doThing();
    $timeout.flush();
    expect(MyService.doThing).toHaveBeenCalled();
  });

  it('should not call doThing for invalid inputs', function () {
    spyOn(MyService, 'doThing').andCallThrough();

    vm.invalidInput = true;
    vm.doThing();
    expect(MyService.doThing).not.toHaveBeenCalled();
  });

});

С первым тестом мы ожидаем позвонить MyService.doThing() функция. С другой стороны, если у вас есть invalidInput как trueпредыдущая функция не должна вызываться.

Я надеюсь, что это помогает.

Другие вопросы по тегам