-
Notifications
You must be signed in to change notification settings - Fork 39
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Story-specific handler / request persisting after switching stories #82
Comments
I'm having this exact same issue - and it was related to when I started specifically passing in multiple mocks to the msw handler. I still don't know what's causing the issue, but thanks for your workaround. Example of what caused it:
|
This seems to be an issue with msw 0.4x, as we don't see the problem with 0.3x. @yannbf I've recreated this problem in a branch of the mealdrop app here: yannbf/mealdrop@main...aaronmcadam:mealdrop:am-persisting-mocks Any ideas? |
I also experienced this when mocking different graphql query responses within the same storybook file. @mmirus's solution does work. Please let us know if this issue is addressed. Thanks! |
I think if we had some way of resetting handlers like we can in unit tests (with |
Also experiencing this with rest handlers. Can't seem to override global handlers I defined in my preview file either. |
Noticing this aswell using 1.6.3. Thankfully doesn't affect chromatic tests. For now i need to refresh the page to get the correct handlers to load for that story. Not the solution of course. |
I kinda work around this by setting RTK's |
I've run into this issue and noticing that the way MSW documentation covers reseting handlers between tests and closing the server after each test file has completed. Storybook documentation also covers how to reuse stories in test. By then introducing msw-storybook-handler:
For reusing a server, perhaps Ex:
It's a little clunky, though. Would appreciate other's thoughts. Currently, I've resorted to checking the test environment in .storybook/preview.js and rewriting the mswDecorator to ensure things are cleaned up. |
How are you doing that? |
Copy/pasting the internals of import {server} from '../src/mock/server';
const isTest = process.env.NODE_ENV === 'test';
if (!isTest) {
initialize()
}
export const decorators = [
isTest
? (storyFn, context) => {
const {
parameters: {msw},
} = context;
if (msw) {
if (Array.isArray(msw) && msw.length > 0) {
// Support an Array of request handlers (backwards compatability).
server.use(...msw);
} else if ('handlers' in msw && msw.handlers) {
// Support an Array named request handlers
// or an Object of named request handlers with named arrays of handlers
const handlers = Object.values(msw.handlers)
.filter(Boolean)
.reduce(
(handlers, handlersList) => handlers.concat(handlersList),
[]
);
if (handlers.length > 0) {
server.use(...handlers);
}
}
}
return storyFn();
}
: mswDecorator
]; |
Here's something a little better I have working: export const mswDecoratorFactory = (api) => {
const onUnmount = () => {
api.close?.() || api.stop();
api.resetHandlers();
};
return (Story, context) => {
const initialRender = useRef(true);
const {
parameters: {msw},
} = context;
if (msw && initialRender.current) {
let handlers = [];
if (Array.isArray(msw)) {
handlers = msw;
} else if ('handlers' in msw && msw.handlers) {
handlers = Object.values(msw.handlers)
.filter(Boolean)
.reduce(
(handlers, handlersList) => handlers.concat(handlersList),
[]
);
}
if (handlers.length > 0) {
api.listen?.() || api.start();
api.use(...handlers);
}
}
useEffect(() => {
initialRender.current = false;
return onUnmount;
}, []);
return <Story />;
};
}; Usage: export const decorators = [
mswDecoratorFactory(!!process?.versions?.node ? require('path/to/server').server : require('path/to/browser').worker)
]; In my case, I'm also using |
I'm having the same issue as this. I tried to import all my handlers into export const marketHandler = rest.get(
`some/coin/route`,
(req, res, ctx) => {
return res(ctx.json(mockMarketPrices));
}
);
export const loadingHandler = rest.get(
`some/coin/route`,
(req, res, ctx) => {
return res(ctx.delay('infinite'));
}
);
export const allNamedHandlers = {
marketHandler,
loadingHandler
} // preview.js
export const parameters = {
msw: {
handlers: allNamedHandlers,
}
} export const MockedSuccess = Template.bind({});
MockedSuccess.parameters = {
msw: {
handlers: { marketHandler },
},
};
export const MockedLoading = Template.bind({});
MockedLoading.parameters = {
msw: {
handlers: { loadingHandler },
},
}; |
I thought I had the same issue, but it turned out that my problem was actually this one : mswjs/msw#251 (comment) (I am using Apollo and the requests were cached and never re-executed) |
@JohnValJohn Did you find a way to clear the cache when switching stories? |
edit: I just found out that the cache object has a reset method. So I ended up importing the cache object used by Apollo and just calling the reset method. Creation of apollo client: export const cache = new InMemoryCache();
export const client = new ApolloClient({cache}); And then in a Decorator: cache.reset() |
I had the same issue |
Also suffering from this. Added the |
I've been watching this issue since I also implemented the workaround of reload(). It would be nice to have MSW clean up on story switches. |
This really causes issues when you have autodocs enabled. The reload hack makes the Default and Loading stories work correctly but then where they load together in the docs page everything is showing the loading skeleton. If I move my non loading handler to the meta section then all stories including the loading story are using the non-loading handler. parameters in my stories don't seem to override parameters in the meta section. |
Is this an issue with the addon, msw, or storybook? By the way, this is storybook 7 / vite/ react 18 I'm using. I replaced the addon by removing it and directly calling setupWorker in my stories file and get the same results. Also I added a couple of fake handlers with console.log and added console.log to my handlers and one at the top of the actual component. Here is what I have and the result. //...imports
const defaultWorker = setupWorker(
rest.get(BASE_URL, (_req, res, ctx) => {
console.log("from the default worker");
return res(ctx.json(restaurants));
})
);
const loadingWorker = setupWorker(
rest.get(BASE_URL, (_req, res, ctx) => {
console.log("from the loading worker");
return res(ctx.delay("infinite"));
})
);
const foo = () => {
console.log("foo");
};
const bar = () => {
console.log("bar");
};
const getRestaurants = () => {
defaultWorker.start();
};
const getNoResponse = () => {
loadingWorker.start();
};
const meta: Meta<typeof RestaurantsSection> = {
title: "Pages/HomePage/Components/RestaurantsSection",
component: RestaurantsSection,
parameters: {
design: {
type: "figma",
url: "https://www.figma.com/file/3Q1HTCalD0lJnNvcMoEw1x/Mealdrop?type=design&node-id=135-311&t=QGU1YHR0aYc88VDn-4",
},
},
args: {
title: "Our Favorite Picks",
},
};
export default meta;
type Story = StoryObj<typeof meta>;
export const Default: Story = {
parameters: {
handlers: [getRestaurants(), foo()],
},
};
export const Loading: Story = {
parameters: {
handlers: [getNoResponse(), bar()],
},
}; On initial page load of the docs section I don't see the docs result
default story result
loading story result
I don't understand why both stories call both handlers. I would expect each one to overwrite the handler instead of calling both. The foo and bar handlers show it's a storybook issue don't they? |
Since I'm just learning on the meal drop project, I could afford to scrap things and start over. After creating a new vite app with latest storybook 7.0.20 and the same version msw-storybook-addon and now the two stories with different handlers works with the exception of autodocs. So I can only guess something updated in storybook fixed the problem for me. No page refresh needed export const Default: Story = {
name: "Default Story",
parameters: {
msw: {
handlers: [
rest.get(BASE_URL, (_req, res, ctx) => res(ctx.json(restaurants))),
],
},
},
};
export const Loading: Story = {
name: "Loading Story",
parameters: {
msw: {
handlers: [
rest.get(BASE_URL, (_req, res, ctx) => res(ctx.delay("infinite"))),
],
},
},
}; The autodocs issue is that it chooses to display by the last handler by default I guess? So it shows the loading story for the components in the docs page. |
Hey everyone! Thanks for all the discussions, and @aaronmcadam thanks for the reproduction. [MSW] Uncaught exception in the request handler for "GET https://mealdrop.netlify.app/.netlify/functions/restaurants?id=1":
TypeError: response2.headers.all is not a function
at Object.transformResponse (http://localhost:6006/node_modules/.cache/sb-vite/deps/chunk-XODS3P5D.js?v=f2eee344:23901:36)
at handleRequest (http://localhost:6006/node_modules/.cache/sb-vite/deps/chunk-XODS3P5D.js?v=f2eee344:23840:144)
at async http://localhost:6006/node_modules/.cache/sb-vite/deps/chunk-XODS3P5D.js?v=f2eee344:23851:11 That's why the mocks seem to leak, but it turns out that MSW is then broken and can't reset/set up mocks after. I tried with later versions of MSW, including the most recent one To everyone here, can you first try a few things for me:
Thank you so much, and sorry this has been affecting you. Whenever I tried to reproduce this issue reliably, I couldn't. If there is a proper reproduction, it will go a long way to fixing this for everyone! |
@yannbf Hey, just adding to this bug - I think one of the cases where the issue most definitely occurs is when you use |
I'm experiencing this issue with msw v2.2.13, so "upgrade MSW" isn't an option. Also already using |
@yannbf any updates here? still experiencing the same issue in the latest version of storybook + addon |
if you're using const Providers = ({ children }: { children: React.ReactNode }) => (
<ReactQueryProvider queryClient={queryClient}>
{children}
</ReactQueryProvider>
);
const ResetQueries = () => {
const queryClient = useQueryClient();
useEffect(() => {
return () => {
queryClient.resetQueries();
};
}, []);
return null;
};
const decorators: Preview['decorators'] = [
(Story) => (
<Providers>
<Story />
<ResetQueries />
</Providers>
),
]; |
I was having this issue and just got it fixed — in my case it was a misconfiguration on my part. I was doing something like this: export const loaders = [
async (ctx) => {
initialize();
return mswLoader(ctx);
},
]; After quite a bit of debugging I found out that because loaders run once per story, that was reinitiliazing the MSW worker without cleaning up the previous one, which caused both the old and the new requests handlers to run, and the older one to "win". The fix was to do just like the docs recommend: initialize();
export const loaders = [mswLoader]; |
Same for me, but I am using SWR. To solve the issue I used the following decorator: import React from 'react';
import { SWRConfig, mutate } from 'swr';
export const invalidateSWRCache = (Story, { parameters }) => {
if (parameters.invalidateSWRCache) {
mutate(() => true, undefined, { revalidate: false });
return (
<SWRConfig
value={{
dedupingInterval: 0,
revalidateOnFocus: false,
revalidateOnMount: true,
revalidateIfStale: false,
}}
>
<Story />
</SWRConfig>
);
}
return <Story />;
}; then in your story add the |
I also faced the To solve this, in association with the solution above, and also with using different ids for the different requests (to create different URLs), I implemented this arguably hacky solution: import type { Meta, StoryObj } from '@storybook/react';
import { http, HttpResponse } from 'msw';
import { useEffect } from 'react';
import { MyComponent } from './MyComponent';
const InfinitePromise = (returnVal: HttpResponse) => {
let resolve = () => {};
const promise = new Promise<HttpResponse>(res => {
resolve = () => {
res(returnVal);
};
});
return {
promise,
resolve,
};
};
let infinitePromise = InfinitePromise(HttpResponse.json({}));
const meta: Meta<typeof MyComponent> = {
parameters: {
invalidateSWRCache: true,
},
component: MyComponent,
};
export default meta;
type Story = StoryObj<typeof meta>;
export const RendersTheComponent: Story = {
args: {
thingyId: 'some_id',
},
parameters: {
msw: {
handlers: {
getThingy: http.get(`https://example.com/some_id`, () => {
return HttpResponse.json({ thingy_id: 'some_id' });
}),
},
},
},
};
const ResolveOnUnmount = Story => {
useEffect(() => {
return () => {
infinitePromise.resolve();
};
}, []);
return <Story />;
};
export const ShowsLoadingState: Story = {
args: {
thingyId: 'some_other_id',
},
parameters: {
msw: {
handlers: {
getThingy: http.get(`https://example.com/some_other_id`, async () => {
infinitePromise = InfinitePromise(
HttpResponse.json({ thingy_id: 'some_other_id' })
);
return await infinitePromise.promise;
}),
},
},
},
decorators: [ResolveOnUnmount],
}; It won't work if the story you are switching to fires the exact same request to the exact same URL. I assume it's because SWR won't issue another request to the same URL if the previous one is still ongoing, which is of course what you'd expect in this situation. |
Hi there! Thanks for the add-on!
I'm running into a problem with a story that demos a loading state:
ctx.delay('infinite')
. This story works great!Any help debugging / solving this would be appreciated! Please let me know if additional info would be helpful.
It's a bit hard to debug this / pinpoint what part of the stack may be responsible for not ending the pending request and initiating a new one when switching from the Loading story to the next one. Apologies if I'm barking up the wrong tree.
In the meanwhile, since refreshing the browser tab on the second (non-loading) story allows that story to load correctly, I've worked around this by using a decorator that reloads the iframe when leaving the Loading story. This is the only workaround I've found that helps.
The text was updated successfully, but these errors were encountered: