测试 API 调用不会在 Cypress 中发生

Blo*_*die 11 cypress cypress-intercept

我在我的应用程序中实现了 API 数据缓存,这样如果数据已经存在,就不会重新获取。

我可以拦截初始获取

cy.intercept('**/api/things').as('api');
cy.visit('/things')                      
cy.wait('@api')                         // passes
Run Code Online (Sandbox Code Playgroud)

为了测试缓存是否正常工作,我想明确地测试相反的情况。

如何修改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"
Run Code Online (Sandbox Code Playgroud)

最小可重复示例

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

由于我们测试的是阴性结果,因此首先使测试失败很有用。提供上面的 HTML 并使用它来确认测试失败,然后删除fetch(),测试应该通过。

Tes*_*ick 6

我也尝试过,但在路线更改发生后,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)
Run Code Online (Sandbox Code Playgroud)

在 100 次迭代的燃烧测试中运行,这似乎没问题,但在我看来,这种方法仍然有可能出现片状测试。

更好的方法是递归地轮询间谍:

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)
Run Code Online (Sandbox Code Playgroud)


Fod*_*ody 5

附加包cypress-if可以更改默认命令行为。

cy.get(selector)
  .if('exist').log('exists')
  .else().log('does.not.exist')
Run Code Online (Sandbox Code Playgroud)

假设您的 API 调用是在触发它们的操作后 1 秒内进行的 - cy.visit().

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

您将需要覆盖该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)
})
Run Code Online (Sandbox Code Playgroud)

到目前为止,只有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
  }) 
Run Code Online (Sandbox Code Playgroud)