Fixture-first Development

March 3, 2020

When you hear the word "Storybook," you probably think UI libraries. Tools like Storybook and Cosmos have been around for a few years now and do a pretty awesome job of presenting UI components in isolation. What most don't consider, however, is how these tools can go beyond just presenting UI components.

Let's talk about this!

Thinking in state

Consider the typical Button component in a UI library. When designing or implementing this component, one of the key considerations we ask ourselves is

"What states will this button have?"

Things might start off with a few simple states such as default and disabled.

Then comes the interactive states such as hovered and active...

Then primary and secondary...

Then primary disabled hovered and secondary disabled hovered

Before you know it, you have many states to consider and keep tracking.

This is when creating fixtures (or stories) starts to provide some real benefit. A fixture is a way of fixing the state of a component and modelling it in a browser environment. By doing this, we document our many states and also provide a means for quickly reproducing them during development and testing.

Compositional components

Moving higher up the component tree, it's easy to lose this state-first way of thinking about components. As scope increases, the core responsibilities of components don't change

  • Rendering output
  • Triggering side effects

While fixtures don't always help us demonstrate side effects, we can always use them as a means of modelling state.

Working in isolation

One of the first pages in the official React docs - Components and Props - states the following.

Components let you split the UI into independent, reusable pieces, and think about each piece in isolation.

Somewhere along the way, I think we forgot this is why we, as a community, chose to use React and not a page-scoped solution such as jQuery.

While focusing on integration is clearly important, there's huge value in being able to change and test components in isolation.

What this looks like

Here's an example of a Page component that has many states that are dependent on a network request and its response:

Network requests and responses of a Page component

Believe it or not, everything you see above was made in total isolation and without spinning up the full site. Note how we can simulate states of our GraphQL client such as fetching and error without any magic — just fixtures and state.

Because React allows us to think of each piece in isolation, there's much less overhead required to do front-end work than you might think. Sure, we eventually need to bring everything together, but that is a small part of the whole development process.

Creating Fixtures

Depending on what tool you choose to use, the way in which you create fixtures will differ, but the process will almost always be the same.

1. Find the component you want to work on

Each project is different but you'll likely want to create fixtures for macro-components such as pages, forms, cards and modals.

For this example let's assume we're working with a page component that makes a GraphQL request and presents the state of that request to the user.

export const PostsPage = () => { const [getPostsState, refetch] = useQuery({ query: gql` query GetPosts { posts { id title content } } ` }); if (getPostsState.fetching) { return ( <ContentCentered> <Spinner /> </ContentCentered> ); } if (getPostsState.error) { return ( <ContentCentered> <Icon type="warning" /> <h1>Error</h1> <p>{getPosts.error.message}</p> </ContentCentered> ); } if (getPostsState.data.posts.length === 0) { return ( <ContentCentered> <Icon type="empty" /> <h1>No posts found!</h1> </ContentCentered> ); } return ( <Content> {getPostsState.data.posts.map( post => <PostCard key={post.id} {...post} /> )} </Content> ); };

2. Setup the props and contexts for all key states

Once a component has been decided, it's time to work out what key states would be useful to have in a fixture. In our case, the key states of this page component are

  • Fetching
  • Error
  • Empty list
  • Populated list

Here's an example fixture that mocks the key states noted above for the PostsPage component:

const fetchingState = { executeQuery: () => { fetching: true }, }; const errorState = { executeQuery: () => { error: new Error("Something went wrong") }, }; const emptyState = { executeQuery: () => { data: { posts: [] } }, }; const dataState = { executeQuery: () => { data: { posts: [{ id: 1, name: "My post" }] } }, }; export default { fetching: ( <GraphqlProvider value={fetchingState}> <PostsPage /> </GraphqlProvider> ), error: ( <GraphqlProvider value={errorState}> <PostsPage /> </GraphqlProvider> ), empty: ( <GraphqlProvider value={emptyState}> <PostsPage /> </GraphqlProvider> ), data: ( <GraphqlProvider value={dataState}> <PostsPage /> </GraphqlProvider> ) }

Since hooks have replaced high-order components, you're going to find yourself mocking contexts more often, so get used to it!

Note: Most libraries don't document how to mock their context so you might have to dive into some code (or do some console.logs) to find out what the different states of the context look like.

We've now added dedicated documentation on mocking out context to the urql docs and encourage other library authors to do the same!

3. Develop within those fixtures

Once your fixtures are in place, you can test, style, and change logic within components quickly and without distraction! 🎉

GIF depicting code updates in an IDE being reflected in the fixtures

Fixtures can also be used for automated testing such as visual regression, component snapshots, and functional testing.

Be mindful of changes that should be tested on a site-wide deployment, such as changes to network requests, hooking into new context, or just adding a component to the site for the first time. As mentioned earlier, this won't be too often, but in these cases, integration testing is the way to go.

Finding out more

Hopefully, if you've gotten this far, you're interested in trying this out for yourself!

I've put together an example repo that contains source code and live examples (including those used in this post) demonstrating the use of fixtures in a real-world project.

Examples include:

  • Fixtures of network requests & responses
  • Fixtures of modals, forms, and validation
  • Fixtures of ui components
  • Visual regression testing (using... you guessed it, fixtures)

Also, a huge shoutout to the contributors of the project React Cosmos who have made a great tool and documentation on developing with fixtures!

Related Posts

What the Hex?

October 24, 2022
If you’re a designer or frontend developer, chances are you’ve happened upon hex color codes (such as `#ff6d91`). Have you ever wondered what the hex you’re looking at when working with hex color codes? In this post we’re going to break down these hex color codes and how they relate to RGB colors.

Screen and Webcam Mixing and Recording with Web APIs

September 22, 2022
There are great native applications on the market for screen recording and editing. While tools like these include a whole host of powerful editing features, for short-form content that doesn’t require post-processing, they might be overkill. I wanted to explore how far browser technology has come in the way of screen sharing and recording, and attempt to create a tool that would allow me to quickly create short-form technical video content with a little bit of flare.

Theming a React Application with Vanilla Extract

December 1, 2021
In this blog post, we're going to look at theming a React application with Vanilla Extract, which solves a lot of our theming problems in a single CSS-in-JS library.