Closed lifeiscontent closed 4 months ago
Just to shed some more light on why I think this feature should exist, I just recently created an example app in next.js and in order to test the full pages in the project, I ended up having to re-export all the pages so I could import them into storybook without having an apollo client trying to send API calls to production: https://github.com/lifeiscontent/realworld/tree/master/web/src/pages
Love this feature and think it could make Storybook a lot more powerful. Let's figure out how to make it happen!
It should be pretty easy to mock something globally and then provide a convenience function for accessing the currently rendered story ID, as an 80/20 solution.
Hi everyone! Seems like there hasn't been much going on in this issue lately. If there are still questions, comments, or bugs, please feel free to continue the discussion. Unfortunately, we don't have time to get to every issue. We are always open to contributions so please send us a pull request if you would like to help. Inactive issues will be closed after 30 days. Thanks!
Hi! I'm new to this issue 😄 Makes me wonder if it would be possible to do this by tapping into storybook's custom webpack config.
My thinking is the dependency could be mocked out leveraging something like webpack shims.
One tricky thing is that the webpack config is exposed in the storybook settings, but most likely the user would like to define mocks inside their stories. Those two mechanism would probably need a way to communicate 🤔
Just thinking out loud here!
Just pulled this off with a custom Storybook Webpack config, and a Webpack alias.
Example .storybook/main.js
:
const path = require('path');
module.exports = {
stories: ['../stories/**/*.stories.js'],
addons: [
'@storybook/addon-actions',
'@storybook/addon-links',
],
webpackFinal: async (config) => {
config.resolve.alias['next'] = path.resolve(__dirname, './mockNext');
return config;
}
};
Example .storybook/mockNext/link.js
:
function MockLink({
href,
children,
...props
}) {
return (
<a href={href} {...props}>
{children}
</a>
);
}
export default MockLink;
I'm mocking Next's Link
component here, but you should be able to mock any import with this pattern.
Unsure if Storybook needs an API for this, but might be useful to add this pattern to the docs if @shilman approves.
@nickdandakis I don't think this is a correct solution because we should be able to mock/stub imports in runtime.
Example:
Component B
is connected to reduxComponent A
is not connected to redux and uses Component B
If I want to test Component A
with Storybook I shouldn't have to connect it to redux just because Component B
requires it. I likely have a separate story where Component B
is tested so it would be better if I could stub Component B
and not worry about redux when working with Component A
.
This is very similar to how I would test components with jest
. 🙂
Just linking @lifeiscontent excellent addon for Next.js router specifically: https://storybook.js.org/addons/storybook-addon-next-router I'll test that today but it sounds great, I've also opened a related discussion on supporting full pages: https://github.com/vercel/next.js/discussions/28290 Mocking Next.js imports is a first necessary step for this.
For mocking imports more broadly, the way to go is definitely tweaking the Webpack config a bit. I am not necessarily fond of having something as advanced as Jest system, which I find very complex to understand.
I created next-router-mock
to handle this, and it works well with Storybook. You can enable it globally via a decorator, or per-component via a context provider:
https://www.npmjs.com/package/next-router-mock
Hi all, I've been trying to experiment a bit with this as well. Has anyone figured out a more generic solution via webpack? I'm hoping to avoid an alias for all the modules we plan on mocking (there are a lot).
Hi all, I've been trying to experiment a bit with this as well. Has anyone figured out a more generic solution via webpack? I'm hoping to avoid an alias for all the modules we plan on mocking (there are a lot).
Can you elaborate maybe? For Meteor what we did was pointing all meteor/*
imports to the right files or scrapping them. In my experience, using Webpack was not the most suited approach (it's bad at rewriting imports) and Babel would have been more appropriate.
If you use aliases instead, you can definitely generate your webpack alias automatically for instance. What's the reason for having a lot of mocked packages that wouldn't work with Next?
Thanks for the response! We have a mono-repo with a package structure like this:
packages/
package-1/
index.js
__mocks__/
index.js
package-2/
index.js
__mocks__/
index.js
package-3/
index.js
__mocks__/
index.js
In jest all of the imports to mocks get updated automagically. However with webpack I'm hoping for a single config that avoids a manual alias for every mock. i.e. I don't want to end up with the following:
config.resolve.alias['package-1'] = path.resolve(__dirname, './packages/package-1');
config.resolve.alias['package-2'] = path.resolve(__dirname, './packages/package-2');
config.resolve.alias['package-3'] = path.resolve(__dirname, './packages/package-3');
...
I think an automatic generation of aliases by parsing the file structure of the repo would be possible. My understanding is that the webpack config gets computed at build time so if I create __mocks__
directory while the server is running, I would think I need a reboot (maybe not the worst thing).
I took a look at your css-loaders and I think I could modify them to get the functionality I'm looking for, but may be overkill for my situation. I was hoping webpack may have another solution but it seems not.
Thanks for the response! We have a mono-repo with a package structure like this:
packages/ package-1/ index.js __mocks__/ index.js package-2/ index.js __mocks__/ index.js package-3/ index.js __mocks__/ index.js
In jest all of the imports to mocks get updated automagically. However with webpack I'm hoping for a single config that avoids a manual alias for every mock. i.e. I don't want to end up with the following:
config.resolve.alias['package-1'] = path.resolve(__dirname, './packages/package-1'); config.resolve.alias['package-2'] = path.resolve(__dirname, './packages/package-2'); config.resolve.alias['package-3'] = path.resolve(__dirname, './packages/package-3'); ...
I think an automatic generation of aliases by parsing the file structure of the repo would be possible. My understanding is that the webpack config gets computed at build time so if I create
__mocks__
directory while the server is running, I would think I need a reboot (maybe not the worst thing).I took a look at your css-loaders and I think I could modify them to get the functionality I'm looking for, but may be overkill for my situation. I was hoping webpack may have another solution but it seems not.
Let me know if you have something open source, I maintain a monorepo as well: https://github.com/VulcanJS/vulcan-npm
However I try to store global testing/storybook related config and mock stuff at top level to avoid this as much as possible (except of course the stories and unit test themselves), are often global mocks are roughly the same for all packages.
Addon created to mock the module. https://github.com/SoraKumo001/storybook-module-mock
import Link from "next/link";
import React, { FC } from "react";
interface Props {}
/**
* NextHook
*
* @param {Props} { }
*/
export const NextHook: FC<Props> = ({}) => {
return (
<div>
<Link href="/">Before</Link>
</div>
);
};
import { expect } from "@storybook/jest";
import { ComponentMeta, ComponentStoryObj } from "@storybook/react";
import { within } from "@storybook/testing-library";
import * as link from "next/link";
import { createMock, getMock } from "storybook-addon-module-mock";
import { NextHook } from "./NextHook";
const meta: ComponentMeta<typeof NextHook> = {
title: "Components/NextHook",
component: NextHook,
};
export default meta;
export const Primary: ComponentStoryObj<typeof NextHook> = {};
export const Mock: ComponentStoryObj<typeof NextHook> = {
parameters: {
moduleMock: {
mock: () => {
const mock = createMock(link);
mock.mockReturnValue(<div>After</div>);
return [mock];
},
},
},
play: async ({ canvasElement, parameters }) => {
const canvas = within(canvasElement);
expect(canvas.getByText("After")).toBeInTheDocument();
const mock = getMock(parameters, link);
expect(mock).toBeCalled();
},
};
Just pulled this off with a custom Storybook Webpack config, and a Webpack alias.
Example
.storybook/main.js
:const path = require('path'); module.exports = { stories: ['../stories/**/*.stories.js'], addons: [ '@storybook/addon-actions', '@storybook/addon-links', ], webpackFinal: async (config) => { config.resolve.alias['next'] = path.resolve(__dirname, './mockNext'); return config; } };
Example
.storybook/mockNext/link.js
:function MockLink({ href, children, ...props }) { return ( <a href={href} {...props}> {children} </a> ); } export default MockLink;
I'm mocking Next's
Link
component here, but you should be able to mock any import with this pattern. Unsure if Storybook needs an API for this, but might be useful to add this pattern to the docs if @shilman approves.
I have tiny issue with this solution, when you have TS aliases it ignores this mocks, or at least that is what happened in my case. I have storybook configured to be able to read tsconfig aliases and they are working but when I am trying to mock an alias import I have this issue that storybook cannot see them so it goes and uses the actual code and not the mock one 😞 Thanks @SoraKumo001 for introducing this addon but TBF I just need to achieve a simple task for this and it should be possible with this solution, but it is a bit problematic since I wanna mock an alias import.
Do you @SoraKumo001, @nickdandakis - have any idea how can I mock the aliases' imports in storybook?
Mock may work if you apply Mock to 'mockNext/link.js'
@SoraKumo001 I did not get your suggestion. Here is my setup, I have a tsconf like this:
// ...
"paths": {
"@libs/*": ["libs/*"],
}
// ...
and in my react component:
import {something} from "@libs/something"
import {useGetUser} from "@libs/hooks/use-get-user"
export function Component() { const user = useGetUser(); }
And here is my main.js
module.exports = {
// ...
webpackFinal: async (config) => {
config.resolve.alias = {
...config.resolve.alias,
"@libs/hooks/use-get-user": path.resolve(__dirname, "./mocked-use-get-user"),
}
// ...
}
and here is my stories.tsx
import {somethingElse} from "@libs/somthing-else";
import {Component} from "./component"
export default {
component: Component,
render: <Component />
}
and in my react component:
import {something} from "@libs/something"
import {useGetUser} from "../../../libs/hooks/use-get-user"
export function Component() { const user = useGetUser(); }
And here is my main.js
module.exports = {
// ...
webpackFinal: async (config) => {
config.resolve.alias = {
...config.resolve.alias,
"../../../libs/hooks/use-get-user": path.resolve(__dirname, "./mocked-use-get-user"),
}
// ...
}
and here is my stories.tsx
import {somethingElse} from "@libs/somthing-else";
import {Component} from "./component"
export default {
component: Component,
render: <Component />
}
So now I wanna know why it is not working. JFI my TS aliases are working in general but just when I try to mock them they do not work
Try the following
Use aliases on the component side import {useGetUser} from "@libs/hooks/use-get-user"
On the Storybook side, create a mock using the actual path import {useGetUser} from "... /... /... /libs/hooks/use-get-user"
@SoraKumo001 I did not get what you mean by storybook side, are you talking about .storybook/main.js
or in the stories.tsx
?
If the latter I should say that I do not know how I can mock it since it is not like the jest.mock
, but also inside the component I am using it and not passing it as a prop or something similar.
If you meant the former I should say that in that case it just ignores the "... /... /... /libs/hooks/use-get-user"
altogether since it does not match the import in the component.tsx
Here is what I understood when you said storybook side; open .storybook/main.js
and in the webpackFinal config the mocked value. But
component.tsx
import {something} from "@libs/something"
import {useGetUser} from "@libs/hooks/use-get-user"
export function Component() { const user = useGetUser(); }
stories.tsx
import {somethingElse} from "@libs/somthing-else";
import {Component} from "./component"
export default {
component: Component,
render: <Component />
}
.storybook/main.js
module.exports = {
// ...
webpackFinal: async (config) => {
config.resolve.alias = {
...config.resolve.alias,
"../../../libs/hooks/use-get-user": path.resolve(__dirname, "./mocked-use-get-user"),
}
// ...
}
Because the key ("../../../libs/hooks/use-get-user") does not match the actual import in the component.tsx
("@libs/hooks/use-get-user")
I specified paths in tsconfig.json and it worked without any problems.
"paths": {
"@/*": ["./src/*"]
}
https://github.com/SoraKumo001/storybook-module-mock/blob/master/src/components/Paths/Paths.stories.tsx https://sorakumo001.github.io/storybook-module-mock/?path=/story/components-paths--path-mock
Just pulled this off with a custom Storybook Webpack config, and a Webpack alias.
Example
.storybook/main.js
:const path = require('path'); module.exports = { stories: ['../stories/**/*.stories.js'], addons: [ '@storybook/addon-actions', '@storybook/addon-links', ], webpackFinal: async (config) => { config.resolve.alias['next'] = path.resolve(__dirname, './mockNext'); return config; } };
Example
.storybook/mockNext/link.js
:function MockLink({ href, children, ...props }) { return ( <a href={href} {...props}> {children} </a> ); } export default MockLink;
I'm mocking Next's
Link
component here, but you should be able to mock any import with this pattern. Unsure if Storybook needs an API for this, but might be useful to add this pattern to the docs if @shilman approves.
And what if I need to mock something in each Story differently?
I needed to mock the composables from vue-router
and used the viteFinal
configuration option. The component I was writing the story for imports these composables like this:
import { useRoute, useRouter } from 'vue-router'
This would be easy to mock in e.g. Vitest, but in Storybook this seems to require more work. There is a plugin that can mock imports, but only relative ones. So instead, I extended the vite config like this:
// .storybook/main.ts
import path from "path";
import type { StorybookConfig } from "@storybook/vue3-vite";
import { mergeConfig } from 'vite';
const config: StorybookConfig = {
// [...]
async viteFinal (config) {
return mergeConfig(config, {
resolve: {
alias: {
"vue-router": path.resolve(__dirname, "./mocks/vue-router"),
},
}
});
}
};
export default config;
And then I created a stub for the composables I needed:
// .storybook/mocks/vue-router.ts
export const useRouter = () => ({
push: () => {}
});
export const useRoute = () => ({
query: {}
});
This works for me. However, it would be nice to have a solution as simple as storybook.mock('vue-router')
.
Please use the addon https://github.com/ReactLibraries/storybook-addon-module-mock for module mocking. Please open an issue there if it doesn't satisfy your use case. Closing. Please let me know if I should reopen the issue if you feel strongly about it and if the addon doesn't help.
@valentinpalkovic I don't think this should be closed. The add-on you provided is React only, and right now there's no way to achieve mocking in frameworks that don't use Webpack (e.g. Svelte)
@kasperpeulen can you confirm that this has been addressed as part of module mocking project?
Closing this as it has been addressed! Check out Type-safe module mocking in Storybook.
Is your feature request related to a problem? Please describe.
One thing I've noticed is I find myself mocking a lot of imports, e.g. if I am using apollo, I use the
MockedProvider
in storybook, or if I'm using next.js I will mock thenext/router
.in jest, I can just do jest.mock('next/router') and it'll load a file from
__mocks__/next/router.js
it would be nice if storybook could do this as well because then I wouldn't have to rearrange code just to make it easier to test in storybook.
Describe the solution you'd like
an API to mock imports and provide stubs in a different file
Describe alternatives you've considered None, as this is a pretty clear solution to a problem
Are you able to assist bring the feature to reality? maybe, I don't have a ton of experience with webpack, but I imagine that is where a portion of this feature would stem from.
Additional context N/A