Cypress: Add option to cy.route2() to match request body

Created on 12 Sep 2020  路  9Comments  路  Source: cypress-io/cypress

I was trying to cy.wait() some specific GraphQL requests that were made using fetch and using cy.route2() but I don't see an option to add a matcher for the request body. According to the type definition there's not body param

The closest thing is query but it doesn't consider the request body. Is this by design or there are plans to add it?

Current behavior:

All GraphQL queries go to the same endpoint "server.com/graphql" using the POST method. The query itself is sent in the request payload and it's a JSON-like string as follows:

{
  "query": "query ($limit: Int) {
    searchShippingTemplate(limit: $limit) {
      shippingTemplates {
        id
      }
    }
  }",
  "variables": {
    "limit": 25
  }
}

My plan was add a match for searchShippingTemplate like:

cy.route2({
  url: "http://localhost:3000/graphql",
  method: "POST",
  body: "*searchShippingTemplate*",
}).as("getQuery");

But there's no way to do that.

Desired behavior:

Add body option to RouteMatcherOptions in order to match a string in the request body.

It would be even better to be able to pass a function (for debugging purposes) since there's no easy way to know what exactly is .route2() making the comparison against for each RouteMatcherOptions option.

cy.route2({
  url: "http://localhost:3000/graphql",
  method: "POST",
  body: (bodyString) => {
    console.log(bodyString); // In theory we could even mutate the request
    return bodyString;
  },
}).as("getQuery");

Test code to reproduce

This is not a bug and more like a feature request.

Versions

"cypress": "^5.1.0"
internal-priority pknet-stubbing work in progress enhancement

Most helpful comment

It would be a great feature to match GraphQl queries.

All 9 comments

@asumaran yes, this would be nice to add, it is currently not implemented but as you say it would help with some implementations.

You can modify the request body and response body already.

// modify an outgoing request
cy.route2({
  url: "http://localhost:3000/graphql",
  method: "POST"
}, (req) => {
  console.log(req.body)
  req.body.replace('foo, 'bar') // modifies outgoing request
})

You can also use a deferred promise to .wait on a request that you've "matched" via the request handler.

// wait on a dynamically-matched request using a deferred Promise
const p = Cypress.Promise.defer()

cy.route2({
  url: "http://localhost:3000/graphql",
  method: "POST"
}, (req) => {
  if (req.body.includes('searchShippingTemplate')) {
    p.resolve() // resolve the deferred promise
  }
})
// ... do some more Cypress stuff here ...
// now, wait on that deferred Promise to resolve:
cy.wrap(p)

I hope this helps in the meantime while we add a more formal API around this. :)

Thank you @flotwig. I鈥檓 going to try your suggestions.

I'm interested in this as well.

+1 It would be great to add this feature for the same reasons commented here.

It would be a great feature to match GraphQl queries.

@flotwig given Promise.defer() is deprecated, is there any way to do this with aliases? Does returning a promise that is resolved/rejected based on the request work?

cy.route2({
  url: "http://localhost:3000/graphql",
  method: "POST"
}, (req) => {
  return new Promise((resolve, reject) => {
    if (req.body.includes('searchShippingTemplate')) {
      resolve() // resolve the deferred promise
    } else {
      reject()
    }
})}).as(`searchShipping`)

cy.wait(`@searchShipping`)

(edit)

To answer my own question - no :(

馃摀 @bahmutov wrote a great post about graphQL requests and cy.route2 and matching request body's.

馃摀 @m4dc4p Additionally there apparently is a new way to alias specific requests now too.

cy.route2('POST', '/graphql', (req) => {
  if (req.body.includes('mutation')) {
    req.alias = 'gqlMutation'
  }
})
// assert that a matching request has been made
cy.wait('@gqlMutation')

馃摀 @bahmutov wrote a great post about graphQL requests and cy.route2 and matching request body's.

* https://glebbahmutov.com/blog/smart-graphql-stubbing/

I've read it. Unfortunately, it does not cover matching specific requests ...

馃摀 @m4dc4p Additionally there apparently is a new way to alias specific requests now too.

* https://docs.cypress.io/api/commands/route2.html#Aliasing-individual-requests

馃く - that looks like what I need. Thank you!

@flotwig given Promise.defer() is deprecated, is there any way to do this with aliases? Does returning a promise that is resolved/rejected based on the request work?

@m4dc4p bah, I wish they hadn't stuck that deprecated warning on defer, defer is totally fine to use in situations like this imo.

You can create your own deferred promise:

function deferredPromise() {
    let resolve, reject
    const promise = new Cypress.Promise((_resolve, reject) => {
        resolve = _resolve
        reject = _reject
    })
    return { resolve, reject, promise }
}

Works the same as Promise.defer.

https://docs.cypress.io/api/commands/route2.html#Aliasing-individual-requests also works well, especially if you need to match on dynamic criteria.

Was this page helpful?
0 / 5 - 0 ratings