Giter Site home page Giter Site logo

cypress-msw-interceptor's Introduction

cypress-msw-interceptor

A networking layer for Cypress using MSW.

Both Cypress and MSW are amazing technologies, this plugin takes the features of MSW and adapts its API to work with Cypress in a way that cy.route works.

This plugin will start a MSW worker as part of the Cypress runner and intercept fetch requests made from the application being tested. MSW does not need to be installed as part of application. This allows requests to be mocked using the fantastic mocking ability of MSW and/or wait for requests to be completed before continuing in a test.

Installation

To install the package run:

$ npm install cypress-msw-interceptor msw --save-dev
# or
$ yarn add cypress-msw-interceptor msw --dev

Then in cypress/support/index.js add:

import 'cypress-msw-interceptor'

If you need to customize the MSW Worker start options. You can do so like:

import { setMswWorkerOptions }, 'cypress-msw-interceptor'

setMswWorkerOptions({ quiet: true, onUnhandledRequest: 'bypass' })

Next we need initialize msw. Follow the guide form MSW website.

You don't need to configure the worker or create handlers unless you want to use it in your application too. The integration for cypress-msw-interceptor will happen automatically by importing cypress-msw-interceptor.

Lastly, we need to set the baseUrl for Cypress so that Cypress starts at the same address as the application so that the service worker can be registered correctly.

Usage

All examples use @testing-library/cypress. If you don't know it, check it out, it's the best way to write tests in Cypress in my opinion.

Basic Example

To intercept a request use the cy.interceptRequest command:

it('should be able to mock a request with msw', () => {
  cy.interceptRequest(
    'GET',
    'https://jsonplaceholder.typicode.com/todos/1',
    (req, res, ctx) => {
      return res(
        ctx.json({
          userId: 1,
          id: 1,
          title: 'Lord of the rings',
          completed: false,
        }),
      )
    },
  )

  cy.visit('/')
  cy.findByText(/lord of the rings/i).should('be.visible')
})

This test will intercept a GET (method) request to https://jsonplaceholder.typicode.com/todos/1 (route) and respond with the mocked payload returned from the response resolver.

This is very similar to cy.route except it uses MSW to mock the response. To learn more about the features of the response resolver, check out the MSW documentation.

Wait for a request

In Cypress to wait for a request to complete you would have to alias a request and then use cy.wait('@alias) (Cypress Route Documentation).

cypress-msw-interceptor provides a similar API to achieve this:

it('should be able to wait for a request to happen before it checks for the text', () => {
  cy.interceptRequest(
    'GET',
    'https://jsonplaceholder.typicode.com/todos/1',
    (req, res, ctx) => {
      return res(
        ctx.delay(1000),
        ctx.json({
          userId: 1,
          id: 1,
          title: 'Lord of the rings',
          completed: false,
        }),
      )
    },
    'todos',
  )

  cy.visit('/')
  cy.waitForRequest('@todos')
  cy.findByText(/lord of the rings/i).should('be.visible')
})

A request can be aliased using the third or forth (depending if a mock is provided) argument to cy.interceptRequest. The use cy.waitForRequest with the alias with a preceding @ to wait for that request to complete before finding the text on the page. To learn more about Cypress aliases check out the Cypress Aliases Documentation.

cy.interceptRequest will also work with the native .as('alias') chainable from Cypress, but that will not show the pretty badge in the test runner if you do. It's recommended that you use the third or forth argument so you get the best debugging experience.

This can also be done with a request that isn't mocked. This is particularly useful for end to end test:

it("should be able to wait for a request to happen that isn't mocked before it checks for the text", () => {
  cy.interceptRequest(
    'GET',
    'https://jsonplaceholder.typicode.com/todos/1',
    'todos',
  )
  cy.visit('/')

  cy.waitForRequest('@todos')
  cy.findByText(/some known text value/i).should('be.visible')
})

By not providing a response resolver, the request executed as it normally but will allow cypress-msw-interceptor to track the request and wait for the alias.

You could conditionally include the response resolver so the test sometimes runs as an integration test and sometimes as an end to end test:

function shouldMockResponse(fn) {
  return process.env.CYPRESS_E2E === 'true' ? fn : undefined
}

cy.interceptRequest(
  'GET',
  'https://jsonplaceholder.typicode.com/todos/1',
  shouldMockResponse((req, res, ctx) => {
    return res(
      ctx.delay(1000),
      ctx.json({
        userId: 1,
        id: 1,
        title: 'Lord of the rings',
        completed: false,
      }),
    )
  }),
  'todos',
)

This way, you could choose to run some tests end to end in some environments and not others.

Getting the response

In order to be able to run a test both as integration and end to end, it's important to be able to add assertions in your tests that are derived from the response:

it('should be able to get the response and check that the correct text is displayed', () => {
  cy.visit('/')
  cy.interceptRequest(
    'GET',
    'https://jsonplaceholder.typicode.com/todos/1',
    (req, res, ctx) => {
      return res(
        ctx.delay(1000),
        ctx.json({
          userId: 1,
          id: 1,
          title: 'Lord of the rings',
          completed: false,
        }),
      )
    },
    'todos',
  )

  cy.waitForRequest('@todos').then(({ request, response }) => {
    cy.findByText(new RegExp(response.body.title, 'i')).should('be.visible')
  })
})

In the example above, cy.waitForRequest will wait for the request to complete and then return the request and the response which can be used in the assertion.

Asserting the number of requests

Sometimes it's important to know how many times a request was made. This can be checked by using the cy.getRequestCalls:

cy.visit('/')
cy.interceptRequest(
  'GET',
  'https://jsonplaceholder.typicode.com/todos/1',
  'todos',
)

cy.waitForRequest('@todos').then(({ request, response }) => {
  cy.getRequestCalls('@todos').then(calls => {
    expect(calls).to.have.length(1)
  })
})

Updating a mock

Sometimes the same request should respond with different values. cypress-msw-interceptor allows you to update a request by redefining an interceptor definition:

it('should be able to update the mock', () => {
  cy.visit('/')
  cy.interceptRequest(
    'GET',
    'https://jsonplaceholder.typicode.com/todos/1',
    (req, res, ctx) => {
      return res(
        ctx.json({
          userId: 1,
          id: 1,
          title: 'Lord of the rings',
          completed: false,
        }),
      )
    },
    'todos',
  )

  cy.waitForRequest('@todos')
  cy.getRequestCalls('@todos').then(calls => {
    expect(calls).to.have.length(1)
  })
  cy.findByText(/lord of the rings/i).should('be.visible')

  cy.interceptRequest(
    'GET',
    'https://jsonplaceholder.typicode.com/todos/1',
    (req, res, ctx) => {
      return res(
        ctx.json({
          userId: 1,
          id: 1,
          title: 'The outsider',
          completed: false,
        }),
      )
    },
    'todos',
  )
  cy.findByRole('button', { name: /refetch/i }).click()
  cy.waitForRequest('@todos')
  cy.getRequestCalls('@todos').then(calls => {
    expect(calls).to.have.length(2)
  })
  cy.findByText(/the outsider/i).should('be.visible')
})

GraphQL Query

MSW provides an easy way to mock GraphQL queries. To make the same API available cypress-msw-interceptor has custom Cypress extensions to work with that API.

To mock a query with the name of CoursesQuery:

cy.interceptQuery(
  'CoursesQuery',
  (req, res, ctx) => {
    return res(
      ctx.delay(1000),
      ctx.data({
        courses: [
          {
            userId: 1,
            id: 1,
            title: 'GET me some data',
            completed: false,
          },
        ],
      }),
    )
  },
  'courses',
)
cy.visit('/')

cy.findByRole('button', { name: /get graphql/i }).click()
cy.waitForQuery('@courses').then(({ response }) => {
  cy.getQueryCalls('@courses').then(calls => {
    expect(calls).to.have.length(1)
  })
  cy.findByText(new RegExp(response.body.data.courses[0].title, 'i')).should(
    'be.visible',
  )
})

This can also be done for a query that hasn't been mocked:

cy.interceptQuery('CoursesQuery', 'courses')
cy.visit('/')

cy.findByRole('button', { name: /get graphql/i }).click()
cy.waitForQuery('@courses').then(({ response }) => {
  cy.getQueryCalls('@courses').then(calls => {
    expect(calls).to.have.length(1)
  })
  cy.findByText(new RegExp(response.body.data.courses[0].title, 'i')).should(
    'be.visible',
  )
})

GraphQL Mutation

In a similar way to queries, there is an extension for GraphQL Mutations:

cy.interceptMutation(
  'UpdateCourse',
  (req, res, ctx) => {
    return res(
      ctx.delay(1000),
      ctx.data({
        courses: [
          {
            userId: 1,
            id: 1,
            title: 'GET me some data',
            completed: false,
          },
        ],
      }),
    )
  },
  'updateCourse',
)
cy.visit('/')

cy.findByRole('button', { name: /mutate graphql/i }).click()
cy.waitForMutation('@updateCourse').then(({ response }) => {
  cy.getMutationCalls('@updateCourse').then(calls => {
    expect(calls).to.have.length(1)
  })
  cy.findByText(new RegExp(response.body.data.courses[0].title, 'i')).should(
    'be.visible',
  )
})

In a similar way, we can wait for requests that weren't mocked:

cy.interceptMutation('UpdateCourse', 'updateCourse')
cy.visit('/')

cy.findByRole('button', { name: /mutate graphql/i }).click()
cy.waitForMutation('@updateCourse').then(({ response }) => {
  cy.getMutationCalls('@updateCourse').then(calls => {
    expect(calls).to.have.length(1)
  })
  cy.findByText(new RegExp(response.body.data.courses[0].title, 'i')).should(
    'be.visible',
  )
})

Things I wish the Cypress plugin API would allow me to do

  • Would be great if Cypress could host the service worker or serve static files. It would be nice not to have to put it in the public folder of the application.

Contributing

To start the development environment run:

yarn install
yarn start

To run the Cypress tests run while the application is running in another terminal:

yarn run cypress:open

cypress-msw-interceptor's People

Contributors

deshiknaves avatar mwarger avatar raphaeleidus avatar ryanfiller avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar

cypress-msw-interceptor's Issues

Service Worker script does not exist at the given path

error on import 'cypress-msw-interceptor'

says

Failed to register a Service Worker for scope ('http://localhost:51138/') with script ('http://localhost:51138/mockServiceWorker.js'): Service Worker script does not exist at the given path.

I haven't gotten to actually writing any special test scenario. Just a simple test to cy.visit page.

Doesn't work with Next & TS

"cypress": "8.6.0",
"cypress-msw-interceptor": "^2.0.0",
"msw": "^0.35.0",

Hey there, I'm trying to make this work with a project that has NextJS and TS support.

I already had msw set up, and I followed the docs for this package, but when I open cypress I get this:

Error: Webpack Compilation Error
./node_modules/cypress-msw-interceptor/src/support.js 41:14
Module parse failed: Unexpected token (41:14)
You may need an appropriate loader to handle this file type, currently no loaders are configured to process this file. See https://webpack.js.org/concepts#loaders
|
| function registerRequest(request) {

if (request?.body?.operationName) {
| registerGraphQL(request)
| }
@ ./node_modules/cypress-msw-interceptor/index.js 4:0-27

Feature request for using existing msw handlers outside of cypress

Hi,

Would you be open to adding the capability of setting up existing msw handlers that have been setup outside of cypress?
I've patch packaged in internally to do what i needed it to do for my own custom setup

Just involved adding a new workerHandlers variable and if that is defined then use it during msw setup and also ignore any msw use during the intercept cypress commands

MSW Failed to Init

Hello, Ive migrated from cypress 9.7.0 to 10.8.0 and MSW no longer seems to work. I am seeing the following error.

[MSW] Failed to register a Service Worker for scope ('http://localhost:35607/') with script ('http://localhost:35607/mockServiceWorker.js'): Service Worker script does not exist at the given path.

Did you forget to run "npx msw init <PUBLIC_DIR>"?

Learn more about creating the Service Worker script: https://mswjs.io/docs/cli/init

Because this error occurred during a before all hook we are skipping all of the remaining tests.

Although you have test retries enabled, we do not retry tests when before all or after all hooks fail

Possibility to make it work with .HAR files

Hi,

Thanks for this project. I am exploring and trying to make Cypress work together with this plugin for MSW: https://github.com/Tapico/tapico-msw-webarchive It works when MSW starts when the app is starting but doesn't work when I try to start MSW inside my Cypress tests, it registers all routes from .HAR file but does not actually return mocked responses. I was wondering if you are aware of that plugin and what's your opinion on that? Is it something we could try to make it work as well ?

Thanks

error "TypeError : cannot read 'type'

This error occurs when cypress starts new test case and the last test case hasn't fully executed then async function composeeRequest yet.

I added

" if (type == undefined) return "

in line:87 to solve the problem.

Capture d’écran de 2021-11-29 19-14-10

I think it's because cypress release immediately resources between each test.

RegExp-typed 'route' parameter are over-escaped and fail compiling

For the cy.interceptRequest command, the cypress-msw-interceptor passes the provided route parameter as MSW's rest[lower case http method](route) (see reference)... But in my case, when that route parameter is a RegExp, MSW responds with status 500 complaining that the provided expression cannot compile with a very escaped string.

Any idea why interceptRequest not working?

Would like to override some of the normal mocks during cypress testing, so installed your plugin. But the interceptRequest doesn't seem to take place and instead I get the default MSW mock that has been setup. Any ideas? I've double/triple checked that I didn't miss any steps. I've double checked that I'm using the right GET/POST and the exact endpoint.

webpack dev server hangs on sequential tests

Hey,

First of all, thanks for this awesome project ;). Saves some time.

I'm having two issues:

  1. I'm running my tests on local webpack dev server in a loop for each resolution provided in a test. First two are passing without issues with test code below, however third one timesout on cy.visit(). Like it makes webpack dev server to hang(?). I have no issues while running my tests without this plugin.
  2. The request that I'm waiting for is POST to graphql API. But I'm not trying to mock anything just want to track it and wait for it and it seems to work properly for first two runs.
  3. After looking at browser console in cypress, I'm able to see this error (maybe related?):

Screenshot 2020-09-25 at 14 22 00

Related test code:

describe('Test different resolutions', () => {
	context('desktop, mobile and tablet resolutions', () => {
		const allSizes = [...];
		allSizes.forEach((resolution) => {
			it(`Should display exchange properly on ${resolution} screen`, () => {
				cy.viewport(resolution[0], resolution[1]);
				cy.interceptRequest(
					'POST',
					'https://somexternalurl.com/api'
				).as('api');
				cy.visit('http://localhost:3000');
				cy.waitForRequest('@api');
				cy.screenshot(`${resolution[0]}-${resolution[1]}`);
			});
		});
	});
});

Adding typings?

Hi,

Could you adding typings to this library?

I've setup the following myself

declare global {
  // eslint-disable-next-line @typescript-eslint/no-namespace
  namespace Cypress {
    interface Chainable {
      waitForRequest(alias: string): Chainable<{ id: string, request: MockedRequest, complete: boolean }>;
      waitForQuery(alias: string): Chainable<{ id: string, request: MockedRequest, complete: boolean }>;
      waitForMutation(alias: string): Chainable<{ id: string, request: MockedRequest, complete: boolean }>;
      getRequestCalls(alias: string): Chainable<void>;
      getQueryCalls(alias: string): Chainable<void>;
      getMutationCalls(alias: string): Chainable<void>;
      interceptRequest(type, route, ...args): Chainable<string>;
      interceptQuery(name, ...args): Chainable<string>;
      interceptMutation(name, ...args): Chainable<string>;
    }
  }
}

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.