Проверьте, что вызов API НЕ происходит в Cypress

Я реализовал кэширование данных API в своем приложении, чтобы, если данные уже присутствуют, они не извлекались повторно.

Я могу перехватить начальную выборку

cy.intercept('**/api/things').as('api');
cy.visit('/things')                      
cy.wait('@api')                         // passes

Чтобы проверить, работает ли кеш, я хотел бы явно проверить обратное.

Как я могу изменить поведение cy.wait() аналогично тому, как .should('not.exist') изменяет cy.get(), чтобы разрешить отрицательную логику?

// data is cached from first route, how do I assert no call occurs?
cy.visit('/things2')                      
cy.wait('@api')                    
  .should('not.have.been.called')   // fails with "no calls were made"

Минимальный воспроизводимый пример

<body>
  <script>
    setTimeout(() => 
      fetch('https://jsonplaceholder.typicode.com/todos/1')
    }, 300)
  </script>
</body>

Поскольку мы проверяем отрицательный результат, полезно сначала сделать так, чтобы тест провалился. Отправьте приведенный выше HTML-код и используйте его, чтобы подтвердить, что тест не пройден, затем удалите fetch(), и тест должен пройти.

Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
9
0
231
3
Перейти к ответу Данный вопрос помечен как решенный

Ответы 3

Ответ принят как подходящий

Дополнительный пакет cypress-if может изменить поведение команды по умолчанию.

cy.get(selector)
  .if ('exist').log('exists')
  .else().log('does.not.exist')

Предположим, что ваши вызовы API выполняются в течение 1 секунды после действия, которое их вызовет — cy.visit().

cy.visit('/things2')
cy.wait('@alias', {timeout:1100})
  .if (result => {
    expect(result.name).to.eq('CypressError')    // confirm error was thrown
  })  

Вам нужно будет перезаписать команду cy.wait(), чтобы проверить цепочку команд .if ().

Cypress.Commands.overwrite('wait', (waitFn, subject, selector, options) => {

  // Standard behavior for numeric waits
  if (typeof selector === 'number') {
    return waitFn(subject, selector, options)
  }

  // Modified alias wait with following if ()
  if (cy.state('current').attributes.next?.attributes.name === 'if') {
    return waitFn(subject, selector, options).then((pass) => pass, (fail) => fail)
  }

  // Standard alias wait
  return waitFn(subject, selector, options)
})

Пока только cy.get() и cy.contains() перезаписываются по умолчанию.


Пользовательская команда для той же логики

Если синтаксис if () кажется вам неправильным, ту же логику можно использовать в пользовательской команде.

Cypress.Commands.add('maybeWaitAlias', (selector, options) => {
  const waitFn = Cypress.Commands._commands.wait.fn

  // waitFn returns a Promise
  // which Cypress resolves to the `pass` or `fail` values
  // depending on which callback is invoked

  return waitFn(cy.currentSubject(), selector, options)
    .then((pass) => pass, (fail) => fail)

  // by returning the `pass` or `fail` value
  // we are stopping the "normal" test failure mechanism
  // and allowing downstream commands to deal with the outcome
})

cy.visit('/things2')
cy.maybeWaitAlias('@alias', {timeout:1000})
  .should(result => {
    expect(result.name).to.eq('CypressError')    // confirm error was thrown
  }) 

Изящный маленький трюк, который я узнал из курса Сети Глеба.

Вы захотите использовать cy.spy() с вашим перехватом и использовать cy.get() для псевдонима, чтобы иметь возможность проверить, что звонки не были сделаны.

// initial fetch
cy.intercept('**/api/things').as('api');
cy.visit('/things')                      
cy.wait('@api')

cy.intercept('METHOD', '**/api/things', cy.spy().as('apiNotCalled'))
// trigger the fetch again but will not send since data is cached
cy.get('@apiNotCalled').should('not.been.called')

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

Blondie 14.09.2022 21:50

Это общий ответ. Без воспроизводимого примера это то, что я могу предоставить.

jjhelguero 15.09.2022 06:27

Я также пробовал cy.spy(), но с жестким cy.wait(), чтобы избежать задержки в приложении после изменения маршрута.

const spy = cy.spy()
cy.intercept('**/api/things', spy)

cy.visit('/things2')
cy.wait(2000)
  .then(() => expect(spy).not.to.have.been.called)

Запуск теста на сжигание из 100 итераций, это кажется нормальным, но при таком подходе все еще есть шанс ненадежного теста, IMO.

Лучшим способом было бы рекурсивно опросить шпиона:

const spy = cy.spy()
cy.intercept('**/api/things', spy)

cy.visit('/things2')

const waitForSpy = (spy, options, start = Date.now()) => {
  const {timeout, interval = 30} = options;

  if (spy.callCount > 0) {
    return cy.wrap(spy.lastCall)    
  }

  if ((Date.now() - start) > timeout) {
    return cy.wrap(null)
  }

  return cy.wait(interval, {log:false})
    .then(() => waitForSpy(spy, {timeout, interval}, start))
}

waitForSpy(spy, {timeout:2000})
  .should('eq', null)

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