Msw: Possible to mock an img src url?

Created on 18 Nov 2020  路  4Comments  路  Source: mswjs/msw

Environment

| Name | Version |
| ------- | ------- |
| msw | ^0.21.3 |
| browser | Chrome 87 |
| OS | Mac 10.15.7 |

Request handlers

import { rest } from 'msw'

export const handlers = [
  rest.get('https://api.flickr.com/services/rest/', (req, res, ctx) => {
    return res(
      ctx.status(200),
      ctx.json({
        photos: {
          photo: [...Array(10).keys()].map((i) => ({
            id: '456',
            secret: '789',
            server: '123',
            farm: 666,
          })),
        },
      })
    )
  }),

  rest.get(
    'https://farm666.staticflickr.com/123/456_789_z.jpg',
    (req, res, ctx) => {
      return res(
        ctx.status(200),
        <WHAT TO PUT HERE ?>
      )
    }
  ),
]
import React from 'react'
import styles from './Photo.module.css'

function Photo({ farm, server, id, secret, title }) {
  const src = `https://farm${farm}.staticflickr.com/${server}/${id}_${secret}_z.jpg`
  return (
    <img
      width="100%"
      className={styles.container}
      data-testid="flickr-grid-photo"
      src={src}
      alt={title}
    />
  )
}

export default Photo

Actual Question

When a site is fetching images from a remote server (via the src attribute in the img elements) - is it possible to intercept that request and serve a local image instead?

bug discussion question browser

Most helpful comment

Thank you very much @kettanaito and @marcosvega91 for such quick help.

The example code works like a charm

import React from 'react';
import {setupWorker, rest} from 'msw'
import ReactDOM from 'react-dom';
import App from './App';
import funnyApp from './funnyApp.png'
import funnyIndex from './funnyIndex.jpg'

const worker = setupWorker(
  rest.get('https://i.insider.com/5c8045a4d2ce7802a110ce79', async (_, res, ctx) => {
    const image = await fetch(funnyApp).then((res) =>
      res.arrayBuffer(),
    )
    return res(
      ctx.set('Content-Length', image.byteLength.toString()),
      ctx.set('Content-Type', 'image/png'),
      ctx.body(image),
    )
  }),
  rest.get('https://i.ytimg.com/vi/Zo_Y-n__Cbc/maxresdefault.jpg', async (_, res, ctx) => {
    const image = await fetch(funnyIndex).then((res) =>
      res.arrayBuffer(),
    )
    return res(
      ctx.set('Content-Length', image.byteLength.toString()),
      ctx.set('Content-Type', 'image/jpeg'),
      ctx.body(image),
    )
  })  
)

worker.start().then(() =>{
  ReactDOM.render(
    <React.StrictMode>
      <App />
    </React.StrictMode>,
    document.getElementById('root')
  );
})

It allows me to mock out the images in my cypress test - which I believe will prove very useful for testing styling with screenshots.

Screen Shot 2020-11-19 at 20 28 26

All 4 comments

Hey, @Norfeldt.

That's a great question. A service worker is able to intercept the request for such static assets and mock them as well. The trouble here is that the current lifecycle of MSW doesn't support that. I will elaborate below on the what's and why's.

Lifecycle

Let's imagine we refresh the page where MSW is activated in your JavaScript and you also have a static <img> rendered somewhere.

  1. Event: beforeunload. When the current window unloads, MSW signals the worker to disable itself. That is so you don't mock the initial HTML/JS files upon the next initial load, resulting into a broken page.
  2. Document loads. The browser parses HTML/CSS/JS.
  3. When it reaches JS execution, your code calls worker.start() and signals to the idle worker that it should become active to intercept runtime requests.

The issue is that the static assets loading happens in the 2nd phase when the worker is idle. In other words, if you render an <img> component during your application's runtime, and not initial load, you should be able to mock the response as you've suggested in your post. The key here is that the image (and the asset request alongside it) has appeared _after_ the worker has been activated and ready to intercept requests.

Thoughts

This is generally a good concern, as mocking static assets can also be useful. There are the following challenges to make such support land in our public API:

  1. Prevent mocking of vital initial assets (such as index.html or your JS files), as this would crash the app.
  2. Support the worker self-destruction when the last client closes. This is currently achieved by the beforeunload event listener and de-activation of the worker.

@marcosvega91, @timdeschryver, may I please ask for your opinion on this?

This is a good question, I have mocked images in a project but the were loaded later and not in the initial initialization.
I'm pretty sure that it will be very useful.

Using MSW with react for example it's already possibile doing this because we can render the application after MSW initialization like in this example.

In the example repo I have also added an image in the index.html and will be not mocked because the worker is not started yet when the image will be loaded.

I think that this is not really simple to implement but it will be useful

Thank you very much @kettanaito and @marcosvega91 for such quick help.

The example code works like a charm

import React from 'react';
import {setupWorker, rest} from 'msw'
import ReactDOM from 'react-dom';
import App from './App';
import funnyApp from './funnyApp.png'
import funnyIndex from './funnyIndex.jpg'

const worker = setupWorker(
  rest.get('https://i.insider.com/5c8045a4d2ce7802a110ce79', async (_, res, ctx) => {
    const image = await fetch(funnyApp).then((res) =>
      res.arrayBuffer(),
    )
    return res(
      ctx.set('Content-Length', image.byteLength.toString()),
      ctx.set('Content-Type', 'image/png'),
      ctx.body(image),
    )
  }),
  rest.get('https://i.ytimg.com/vi/Zo_Y-n__Cbc/maxresdefault.jpg', async (_, res, ctx) => {
    const image = await fetch(funnyIndex).then((res) =>
      res.arrayBuffer(),
    )
    return res(
      ctx.set('Content-Length', image.byteLength.toString()),
      ctx.set('Content-Type', 'image/jpeg'),
      ctx.body(image),
    )
  })  
)

worker.start().then(() =>{
  ReactDOM.render(
    <React.StrictMode>
      <App />
    </React.StrictMode>,
    document.getElementById('root')
  );
})

It allows me to mock out the images in my cypress test - which I believe will prove very useful for testing styling with screenshots.

Screen Shot 2020-11-19 at 20 28 26

I'm a bit late to the party, but I agree 馃憤

Was this page helpful?
0 / 5 - 0 ratings