Integration testing with MSW dynamic mocks

MSW and cypress/playwright are awesome testing tools. However, things can get tricky when you combine them. The crux of the problem is that your test code is not in the same process that your app code is. They are two discreet processes so you don't have direct access to the msw server instance.

With msw, you typically define the "happy path" responses in a mocks/handlers.ts file and this is used in your mock server. But...what if you want to test an error scenario?

You have a few options.

Dynamic mock scenarios

Basically split up the handlers.ts into a scenarios object and add a query parameter to "activate" a certain scenario. You can see this in the msw docs here.

There are two less-than-desirable traits to this approach though:

  • They're separate from the test code
  • You have to come up with your own arbitrary scenario naming convention

Use a development branch of msw

There is work being done to bridge the gap between msw server and e2e test code. It's just not ready for prime time yet. The setupRemoteServer API allows you to communicate to your server over a web socket connection. The idea is very promising and I'm excited for it to get merged! You can help by funding the maintainer's development efforts.

Development only app endpoint

I came up with a low-tech setupRemoteServer alternative to using network behavior overrides in a dedicated test endpoint. I'm using Remix so I simply created a development-only action route that I can make requests to that will interact with the msw server for me.

Now, I can set up a scenario directly from the e2e test.

The loader of my /app/contacts route makes an api call to fetch data from a http://localhost:6000/people api. The above code allows me to mock that request directly in my test code, keeping it all together.

I can further clean this up by abstracting this into a mock() function.

Hope this helps!

Categories: Remix