История тестирования Ember действительно великолепна, но время от времени вы можете столкнуться с ситуацией, которая слишком далеко отклоняется от проторенной дорожки, чтобы ее можно было преодолеть обычными методами.

Это означает, что вам придется копнуть немного глубже, чтобы ваши тесты прошли успешно, но, в зависимости от сценария (и типа теста), есть несколько подходов, которые полезно знать.

Использование registerWaiter для ожидания завершения асинхронного кода

Этот метод работает как для интеграционных, так и для приемочных тестов, что делает его весьма универсальным.

Это полезно, когда асинхронное поведение выходит за рамки обычного кода цикла выполнения Ember, и поэтому используется andThen (для принятия) или wait () (для интеграции) одного не достаточно.

Хорошим примером этого является поведение, запускаемое обработчиком события загрузки изображения.

В самом простом случае код может быть примерно таким:

const img = new Image();
img.onload = event => {
  triggerSomeAction();
}
img.src = 'http://image.url/path.jpg';

Цикл бега

Первое, что следует отметить в этом коде, это то, что обратный вызов в настоящее время не включен в цикл выполнения.

В руководствах Ember есть хорошее объяснение (которое стоит прочитать полностью) того, почему это необходимо, и каковы его последствия при тестировании.

… Некоторые из тестовых помощников Ember - это обещания, которые ждут, пока цикл выполнения не опустеет, прежде чем разрешиться. Если в вашем приложении есть код, который выполняется вне цикла выполнения, он будет разрешен слишком рано и приведет к ошибочным ошибкам тестирования, которые трудно найти ...

Итак, в нашем примере результирующий код будет:

const img = new Image();
img.onload = event => {
  Ember.run(() => {
    triggerSomeAction();
  });
}
img.src = 'http://image.url/path.jpg';

Обещания

Следующим шагом к обузданию асинхронного кода является следование подходу Ember с использованием обещаний. Опять же, руководства предоставляют хорошее объяснение, и их стоит прочитать полностью.

Короче говоря, обещания - это объекты, которые представляют собой конечную ценность. Обещание может быть выполнено (успешно разрешено значение) или отклонено (не удалось разрешить значение). Получить это конечное значение или обработать случаи, когда обещание отклоняется, можно с помощью метода обещания then (), который принимает два необязательных обратных вызова: один для выполнения, а другой - для отклонения.

Мы можем обернуть этот код в обещание следующим образом:

let promise = new Ember.RSVP.Promise((resolve, reject) => {
  const img = new Image();
  img.onload = event => {
    Ember.run(() => {
      resolve(img);
    });
  };
  img.onerror = () => {
    Ember.run(() => reject());
  };
  img.src = 'http://image.url/path.jpg';
});

Так что теперь поведение загрузки и ошибки инкапсулируется в обещание.

Интеграционные тесты

В случае интеграционного теста вы можете приостановить тест, чтобы дождаться завершения всего асинхронного поведения, используя помощник по тестированию wait (). Из гидов:

Часто взаимодействие с компонентом вызывает асинхронное поведение, такое как HTTP-запросы или таймеры. Помощник ожидания предназначен для обработки этих сценариев, предоставляя ловушку, чтобы гарантировать, что утверждения сделаны после того, как все запросы и таймеры Ajax завершены.

В основном тест будет выглядеть так:

import { moduleForComponent, test } from 'ember-qunit';
import wait from 'ember-test-helpers/wait';
import hbs from 'htmlbars-inline-precompile';
moduleForComponent('my component', 'working', {
  integration: true
});
test('works', function(assert) {
  
  this.render(hbs`{{my-component src='http://emberjs.com/images/tomsters/sandiego-zoey.png'}}`);
  
  return wait().then(() => {
    assert.equal(someIndicator, 'image loaded');
});

Приемочные испытания

На данный момент (до тех пор, пока не появится единая история тестирования Роберта Джексона), чтобы сделать это в приемочных тестах, немного отличается и использует andThen (). Из гайдов по этому помощнику ожидания.

Помощник andThen будет ждать завершения всех предыдущих асинхронных помощников, прежде чем продолжить работу. Давайте посмотрим на следующий пример.

В основном тест будет выглядеть так:

import { test } from 'qunit';
import moduleForAcceptance from '../../tests/helpers/module-for-acceptance';
moduleForAcceptance('image loaded');
test('/', function(assert) {
  visit('/');
andThen(function() {
    assert.equal(someIndicator, 'image loaded');
  });
});

зарегистрироваться

К сожалению, для этого конкретного варианта использования выполнения этих шагов недостаточно. Причина в том, что не все обещания или циклы выполнения включаются по умолчанию при определении того, что заставляет помощников тестирования ждать. В целом запросы и таймеры AJAX должны работать автоматически, но другие сценарии не так понятны.

Чтобы сделать это явным, мы хотим, чтобы тесты ожидали загрузки наших изображений, нам нужно зарегистрировать их самостоятельно.

Руководства более ограничены в этом, но документы API объясняют

Это позволяет ember-testing прекрасно взаимодействовать с другими асинхронными событиями, такими как приложение, ожидающее перехода CSS3 или транзакции IndexDB.

Фактически, как только официант зарегистрирован, тест ожидает и опрашивает метод, пока он не вернет истину.

Что мне не очень нравится в этом, так это то, что это означает, что вы должны включать код, предназначенный только для тестирования, в код вашего приложения. На самом деле это не так уж и плохо, поскольку вы можете довольно легко обернуть код в условный Ember.testing, но, надеюсь, вскоре это станет возможным другими способами.

Итак, в этом примере это может быть достигнуто с помощью init (), чтобы установить для переменной значение false и зарегистрировать официанта следующим образом:

init() {
  this._super(...arguments);
  if (Ember.Testing) {
    this._loading = false;
    Ember.Test.registerWaiter(() => this._loading === false);
  }
}

Затем установите для переменной значение true, пока изображение загружается, и используйте обещание.finally (), чтобы вернуть значение false, как только изображение загрузится следующим образом:

if (Ember.testing) {
  this._loading = true;
  return promise.finally(() => this._loading = false);
}
    
return promise;

После этого и приемочный тест, и интеграционный тест будут знать, что нужно дождаться загрузки изображений.

Примеры Ember Twiddle

Благодаря Ember Twiddle можно запускать тесты QUnit для кода Ember в браузере, поэтому я создал несколько реальных примеров на основе этого варианта использования, чтобы вы могли запускать тесты и вносить изменения, чтобы убедиться, что он работает для себя.

Заключение

Это очень гибкий способ включения в ваши тесты кода, который иначе сложно протестировать. Основным недостатком является то, что он вводит некоторый код, специфичный для тестирования, в базу кода вашего приложения, но, в свою очередь, делает возможным как приемочное, так и интеграционное тестирование без какой-либо дополнительной работы.

Это особенно полезно для интеграционных тестов, которые в настоящее время не имеют помощников для асинхронных пользовательских тестов.

Спасибо за чтение, и если у вас есть какие-либо отзывы, я хотел бы услышать от вас - часть моей мотивации для написания этого материала состоит в том, чтобы выяснить, используют ли другие люди аналогичные подходы или есть лучшие альтернативы.