swille
swille

Reputation: 841

Verify number of times request was made

Using Cypress Intercept to mock the routes and I want to verify the number of times the route was called. So far, I've found nothing in the docs for this. There's mention of cy.spy but it only returns 1, every time. There's a {times:N} object for the intercepted route, but it allows the route to match and succeed any number of times. It doesn't function as a call limit. This is such a common need that I'm sure I'm just missing something, tired eyes and all.

Spy:

cy.intercept({method: 'GET', url:'my-url', middleware:cy.spy().as('myspy')})

Times:

 cy.intercept({method: 'GET', url:'my-url'}, {times:0})

Cypress Feature request: https://github.com/cypress-io/cypress/issues/16655

Upvotes: 7

Views: 11799

Answers (4)

user9622173
user9622173

Reputation:

Cypress intercept is the spy.

The problem is when to check the call count.

For example, http://cypress.io/page-data

it('counts intercepts', () => {

  let requestCounter = 0;
  let responseCounter = 0;

  cy.intercept('page-data/**/*', (req) => {
    requestCounter += 1;                            // count requests
    req.on('response', () => responseCounter += 1 )  // or count responses
  })

  cy.visit('https://www.cypress.io/')

  cy.wait(5000).then(() => {               // arbitrary wait
    expect(requestCounter).to.eq(18)       // since we don't know exactly
    expect(responseCounter).to.eq(18)      // what loads last
  })                                       

})

The answer given by Jennifer Shehane in the linked feature request shows another way using <alias>.all,

it('counts intercepts', () => {

  cy.intercept('page-data/**/*')
    .as('pageData')

  cy.visit('https://www.cypress.io/')

  cy.get('@pageData.all')
    .should('have.length', 18);

})

However, it does not consistently pass. About 1 in 5 runs on my machine fail because the cy.get() responds too early.

Ideally you should be able add a timeout, but this currently has no effect.

cy.get('@pageData.all', { timeout: 10000 })  // does not observe the timeout

Using cy.spy() as a routeHandler allows a timeout to be set on the code that checks the call count.

it('counts intercepts', () => {

  cy.intercept({ url: 'page-data/**/*', middleware: true }, req => {
    req.on('response', (res) => {
      res.setDelay(2000)               // artificial delay to force failure
    })
  })

  const spy = cy.spy();
  cy.intercept('page-data/**/*', spy)

  cy.visit('https://www.cypress.io/')

  cy.wrap({}, { timeout: 10000 })     // adjust timeout to suit worst case page load
    .should(() => {
      console.log('testing spy')      // see the retry happening (90+ logs)
      expect(spy).to.be.callCount(18)
    })

})

Upvotes: 15

Rory
Rory

Reputation: 2506

I found this problem to be a rare reason to use a local variable in Cypress:

it('Counts intercepts', () => {

    let count = 0

    cy.intercept('GET','/route', req => {
        count = count+1;
        req.reply({})
    })

    //... your tests
    
    cy.get('body').then( () => {
        expect(count,'Number of times intercepted').to.equal(1) //Assert intercepted only once
    })
})

Upvotes: 4

Undistraction
Undistraction

Reputation: 43351

The following command works.

Note that cy.get doesn't work with the leading @ but it feels more expected to me, so this will work with or without it.

Add this to /cypress/support/commands.js

Cypress.Commands.add(`verifyCallCount`, (alias, expectedNumberOfCalls) => {
  const resolvedAlias = alias[0] === `@` ? alias.substring(1) : alias

  cy.get(`${resolvedAlias}.all`).then((calls) => {
    cy.wrap(calls.length).should(`equal`, expectedNumberOfCalls)
  })
})

Usage:

cy.verifyCallCount(`@createLogEntry`, 3)
// Or
cy.verifyCallCount(`createLogEntry`, 3)

Upvotes: 1

rafaelbiten
rafaelbiten

Reputation: 6240

I was looking for the same thing because we have demo accounts that are not meant to ever reach/call our apis. After doing some research, this is what ended up working for me:

describe('demo accounts', () => {
  beforeEach(function spyOnApiCalls() {
    cy.intercept(/payment-platform/, cy.spy().as('coreApi'))
    cy.intercept(/service-management/, cy.spy().as('mgmtApi'))
  })

  afterEach(function assertApisWereNotCalled() {
    cy.get('@coreApi').its('callCount').should('equal', 0)
    cy.get('@mgmtApi').its('callCount').should('equal', 0)
  })

  it('start test blocks', () => {...})
})

Note how we're passing and aliasing instances of the cy.spy, then later asserting against them. IMO, this also reads pretty well with the its('callCount').should('equal', expectedCallCount).

Here we have beforeEach and afterEach blocks because it made sense in my case, but this approach seems pretty flexible and should work in many other scenarios.

Upvotes: 3

Related Questions