How to stub VueJS (React) container components in Storybook

Alexey Antipov
Vue.js Developers
Published in
4 min readJun 19, 2019

In my previous article, I wrote about what the amazing tool Storybook is and how it helps frontend developers to be more productive and write more robust code. As with any other tool, there are some gotchas in using Storybook that you should be aware of. In this article, I want to tell about one of them — dealing with container components.

The Problem

Storybook allows rendering presentational (pure) components only. And it’s a good thing that forces you to separate concerns — put stateful logic into container components and using presentational components only for rendering UI, making them stateless.

However, there are situations when your presentational components’ children (immediate children or somewhere down the components hierarchy) are not stateless. For example, some child component can connect to API and fetch data. It means that your component is not truly pure component and can’t be rendered by Storybook.

Possible solutions

You could refactor your code and put side-effects logic into some upper container component, but that may complicate the code and doesn’t seem right. You would end up in a situation with one huge container component handling all the side-effects logic.

The other solution could be to stub such inner container components so that Storybook doesn’t need to execute real container components but execute a fake component instead.

Stubbing Container Components

A stubbed container component highlighted by a red border

The idea is that if Storybook finds a container component, then it should take a predefined fake component and render it instead. Therefore, that fake component should serve as a placeholder for container components.

Create the fake component

Let’s start creating the fake component itself.

A fake component to be used instead of container components

It’s just an empty component with a red border and a text “Stubbed container component”. Let’s name it component-stub.vue and put under .storybook folder.

Distinguish container components from presentational ones

To teach Storybook to use the fake component instead of container components, we first need to set a pattern how to separate container and presentational components so that Storybook stubs only container components and leave presentational components untouched.

In my project, I add a container suffix to the filename of container components, therefore the separation is explicit.

Substitute container components by the fake component

Not it’s time for the most interesting thing — the fake mechanism.

Components are bound to each other using ES6 imports. Under the hood, Storybook uses Webpack as a module bundler and Storybook provides an ability to tweak its Webpack configuration via .storybook/webpack.config.js file.

Therefore our solution should be to teach Webpack to use the fake component.

It turned out the solution is very easy thanks to Webpack NormalModuleReplaccementPlugin plugin, which

allows you to replace resources that match resourceRegExp with newResource

So we only need to add the following block to the .storybook/webpack.config.js file:

plugins: [
new webpack.NormalModuleReplacementPlugin(
/\.container\.vue$/,
path.resolve(__dirname, './component-stub.vue')
),
],

Other approaches to stub container components

The problem with stubbing container components in Storybook is not new.

There is a 2-years old issue https://github.com/storybookjs/storybook/issues/1298, and people there suggest different possible solutions.

Some suggest using additional tools to implement stubbing, like proxyquire, mocktail or rewiremock, which I find an overhead for our use-case.

Another suggestion is similar to ours but relies on the use of a custom Webpack loader. The developer is supposed to create fake components next to the real components and Webpack uses the fake component if there is one. That approach may be more powerful (you can create custom fake components for each container component), but more difficult to use — the developer needs to create a custom fake component for each container component.

Our approach can be easily combined with the approach above and we can achieve both flexibility and ease of use by adding the following logic to our solution:

  • if there is a fake component next to the real component, then use the fake one;
  • otherwise, if there is acontainer suffix in the filename, then use the predefined fake component from the .storybook folder
plugins: [
new webpack.NormalModuleReplacementPlugin(
/\.vue$/,
resource => {
const resPath = resource.request;
const mockPath = resPath.slice(0, -3) + 'mock.vue';
const absMockPath = path.resolve(resource.context, mockPath);
const absRootMockPath = path.resolve(
__dirname,
'./component-stub.vue'
);
if (fs.existsSync(absMockPath)) {
resource.request = mockPath;
} else if (resPath.endsWith('container.vue')) {
resource.request = absRootMockPath;
}
}),
],

Conclusion

The solution to the problem of stubbing container components can be achieved easily without installing and configuring any external tools.

Examples in this article use VueJS, but the solution itself is framework agnostic and can be applied to other frameworks in a similar way.

--

--