question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

A Solution for Stubbing GraphQL Requests

See original GitHub issue

Here is a potential solution to stubbing graphql requests in Cypress

Caveats

  • the requests cannot be waited on, but they do return promises that resolve immediately so it shouldn’t be an issue in practice
  • a new visit is required to set or change a stub, so this can have a negative performance impact on the tests
  • assumes your app is using fetch but can probably be modified to work for xhr
  • assumes you are using Cypress v0.20.x (I think)
  • does not provide any error handling

Details

1. Create a Cypress Command to make life easier

/*
 * Create a command named `visitStubbed` that wraps the normal `cy.visit`. It
 * takes the url as the first parameter and * an object with your graphql
 * request stubs. Each key of the object must match the `operationName` of the
 * graphql request to be stubbed and each value is an object containing the
 * stubbed graphql response.
 *
 * Ex.
 * ```
 * cy.visitStubbed('/home', {
 *   fetchWidgets: {
 *     data: {
 *       widgets: [{
 *         id: 1,
 *         name: 'Cool Widget',
 *         __typename: 'Widget',
 *         //...
 *       }]
 *    }
 *  }
 * })
 * ```
 */

// import the function from the following step (optional)
import { runQuery } from '../support/graphql'

Cypress.Commands.add('visitStubbed', function(url, operations = {}) {
  cy.visit(url, {
    onBeforeLoad: win => {
      cy
        // stub `fetch`
        .stub(win, 'fetch')
        
        // your graphql endpoint
        .withArgs('/graphql')

        // call our stub
        .callsFake(serverStub)
    },
  })

  function serverStub(_, req) {
    // parse the request
    const { operationName, query, variables } = JSON.parse(req.body)

    // return the stub if it was provided
    const resultStub = operations[operationName]
    if (resultStub) {
      return Promise.resolve(responseStub(resultStub))
    }
    // else {
    //   return {} 
    // }

    // If you want, fallback to default mock data if stub for operation is not specified (optional)
    return runQuery(query, variables).then(responseStub)
  }
})

function responseStub(result) {
  return {
    json() {
      return Promise.resolve(result)
    },
    ok: true,
  }
}

2. Create a mock GraphQL server (Optional)

You only need this if you want a fallback to a mock server. This requires your schema in SDL format to be exported as a string from a .js file. We handle this by generating the file before running Cypress using get-graphql-schema:

ORIGIN=https://some-url.com

SCHEMA_FILE=cypress/schema.js

echo "module.exports = \`" > $SCHEMA_FILE
node_modules/.bin/get-graphql-schema $URL >> $SCHEMA_FILE
echo "\`" >> $SCHEMA_FILE
import { makeExecutableSchema, addMockFunctionsToSchema } from 'graphql-tools'
import { graphql } from 'graphql'
// Your schema in SDL format exported as a string
import typeDefs from '../schema'

const schema = makeExecutableSchema({ typeDefs })
addMockFunctionsToSchema({ schema })

export function runQuery(query, variables) {
  return graphql(schema, query, {}, {}, variables)
}

Issue Analytics

  • State:closed
  • Created 6 years ago
  • Reactions:33
  • Comments:84 (6 by maintainers)

github_iconTop GitHub Comments

37reactions
yalivcommented, Aug 2, 2018

@egucciar @joshuataylor very nice solution! It works for me.

I just prefer to stick with custom command, so I don’t have to explicitly export and import the function.

If I have this in support/index.js:

import './commands'

Then I’ll add this in support/commands.js:

// --------------------------------------
// Mock GraphQL requests with stubs.
// --------------------------------------
Cypress.Commands.add('mockGraphQL', stubs => {
  cy.on('window:before:load', win => {
    cy.stub(win, 'fetch', (...args) => {
      console.log('Handling fetch stub', args)
      const [url, request] = args
      const postBody = JSON.parse(request.body)
      let promise

      if (url.indexOf('api') !== -1) {
        stubs.some(stub => {
          if (postBody.operationName === stub.operation) {
            console.log('STUBBING', stub.operation)
            promise = Promise.resolve({
              ok: true,
              text() {
                return Promise.resolve(JSON.stringify(stub.response))
              }
            })
            return true
          }
          return false
        })
      }

      if (promise) {
        return promise
      }

      console.log('Could not find a stub for the operation.')
      return false
    })
  })
})

I write my stub in fixtures/allCars.json:

{
  "operation": "allCars",
  "response": {
    "data": {
      "allCars": [
        {
          "id": "cj1ds2x4snt290150j2qoa8tj",
          "year": 2014,
          "make": "Porsche",
          "model": "918 Spyder"
        },
        {
          "id": "cj1ds6g8pnzua0150ekfnmmwe",
          "year": 2014,
          "make": "McLaren",
          "model": "P1"
        },
        {
          "id": "cj1ds83ulnuxz01144k4rrwru",
          "year": 2015,
          "make": "Ferrari",
          "model": "LaFerrari"
        }
      ]
    }
  }
}

And I can use the command in integration/my-page.spec.js:

describe('my page', () => {
  beforeEach(function() {
    // Fetch fixtures.
    cy.fixture('allCars').as('carsQuery')
  })

  context('mock & visit', () => {
    beforeEach(function() {
      cy.mockGraphQL([this.carsQuery])

      cy.visit('http://localhost:8080')
    })

    it('my action', () => {})
  })
})

It looks very neat.

23reactions
jon-thompsoncommented, Mar 13, 2018

Thanks so much for these solutions so far! I used a lot of the code in this discussion to create an entirely separate mockGraphQL command rather than creating an extended visit command:

const responseStub = result => Promise.resolve({
  json() {
    return Promise.resolve(result);
  },
  text() {
    return Promise.resolve(JSON.stringify(result));
  },
  ok: true,
});

Cypress.Commands.add('mockGraphQL', (handler) => {
  cy.on('window:before:load', (win) => {
    const originalFunction = win.fetch;

    function fetch(path, { body, method }) {
      if (path.includes('/graphql') && method === 'POST') {
        return responseStub(handler(JSON.parse(body)));
      }

      return originalFunction.apply(this, arguments);
    }

    cy.stub(win, 'fetch', fetch).as('graphqlStub');
  });
});

With that, I can then simply call mockGraphQL before the visit call in my test:

    cy.mockGraphQL(({ operationName, variables }) => {
      return { data: getAppropriateData(operationName, variables) };
    });

Note that I used the event approach described here rather than overriding visit.

I think this approach is a little tighter and more explicit than the other solutions so far, but that may just be my personal preference. Sharing here in case someone else feels the same way.

Read more comments on GitHub >

github_iconTop Results From Across the Web

Stubbing GraphQL using Cypress - Jay Freestone
A more ergonomic solution to stubbing out multiple GraphQL responses using Cypress.
Read more >
Smart GraphQL Stubbing in Cypress - Gleb Bahmutov
How to spy on and stub GraphQL calls from the application using Cypress and its new cy.route2 API. Testing GraphQL calls; Testing without ......
Read more >
Mock Graphql server with multiple stubs in Cypress
For anyone else hitting this issue, there is a working solution with ... With that is posible to stub exact request queries and...
Read more >
Cypress: Mocking GraphQL Requests | by Viskantas Juodenas
A helper function is going to mock all window.fetch calls that are executed with /graphql endpoint. The fetch stub will look for mock...
Read more >
Cypress and GraphQL - Udacity Eng & Data
This article will detail solutions to these problems. Network Stubbing and Aliasing. Unlike a REST API, GraphQL requests are all sent to a...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found