Интеграционное тестирование асинхронных обработчиков событий DOM - PullRequest
1 голос
/ 10 июля 2019

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

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

Примите во внимание следующее:

const LoadState = {
  Pending: 'pending',
  Loaded: 'loaded',
  Failed: 'failed'
};

const App = {
  state: {
    loadState: LoadState.Pending,
    renderAnimationFrameId: null
  },
  
  ui: {
    loadState: document.getElementById('load-state'),
    loadButton: document.getElementById('load-button')
  },
  
  initialize() {
    this._load = this._load.bind(this);
    this.ui.loadButton.addEventListener('click', this._load);
  },
  
  render() {
    // Throttle rendering to framerate.
    if (this.state.renderAnimationFrameId) {
      return;
    }
  
    this.state.renderAnimationFrameId = requestAnimationFrame(() => {
      this.ui.loadState.textContent = this.state.loadState;
      this.state.renderAnimationFrameId = null;
    });
  },
  
  _load() {
    window.fetch(new Request('flowers.jpg')).then(({ ok }) => {
      this.state.loadState = ok ? LoadState.Loaded : LoadState.Failed;
    }).catch(() => {
      this.state.loadState = LoadState.Failed;
    }).finally(() => {
      this.render();
    });
  }
};

App.initialize();
App.render();
<input id='load-button' type="button" value="Load Data" />

<div id='load-state'>
  Pending
</div>

Предполагая ложную реализацию fetch, я хотел бы убедиться, что load-button правильно реагирует на нажатие и что App корректно обновляется после этого.Примерно так написано довольно интуитивно:

window.fetch = Promise.resolve(true);
describe('App', () => {
    App.initialize();

    it('should load', (done) => {
        App.ui.loadButton.dispatchEvent(new Event('click'));

        // Wait for event to update state after fetch resolution.
        setTimeout(() => {
            expect(App.state.loadState === LoadState.Loaded);
            done();
        });
    });

    it('should render', (done) => {
        App.render();

        // Wait for throttled render
        setTimeout(() => {
            expect(App.state.renderAnimationFrameId === null);
            expect(App.ui.loadState.textContent === LoadState.Loaded);
            done();
        });
    });
});

Эти тесты вносят небольшую ошибку.Первый тест частично ожидал, но не полностью ожидал, чтобы асинхронная логика приложения установилась. Тест проходит, потому что state.loadState является правильным, но вызов render все еще в полете.Второй тест может показывать ошибочные результаты из-за этого.

Мне интересно, какие соответствующие ответы могут быть на это.Некоторые предлагаемые решения включают в себя:

  • Mock setTimeout, setInterval, requestAnimationFrame и ставят в очередь их обратные вызовы вместо выполнения асинхронно.Разрешить синхронное воспроизведение обратных вызовов.Очень волшебное и подверженное ошибкам решение, обещания по-прежнему асинхронные и должны обрабатываться отдельно

  • Переписать производственный код, чтобы обеспечить публичное раскрытие всех ожидающих обещаний.Это делает использование async и await гораздо более сложным и интуитивно понятным.

  • Шпион setTimeout, setInterval, requestAnimationFrame и Promise, попытайтесь сделать выводкакое асинхронное поведение все еще ожидается в результате запуска метода, определите асинхронное разрешение путем опроса шпионов.Это менее сложно, чем выше, но условия гонки и сторонний код становятся более сложными для отладки, и неясно, как будет работать использование await или yield.

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

TL; DR: обработчики событий мыши / касания являются асинхронными и не нуждаются в раскрытии своих внутренних объектов.Как правильно реагировать на потребности тестирования?

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...