How to provide fast and reliable feedback whilst working with third parties

Last year I was working on a React Redux Saga web application which interacted with multiple microservices. At the time, we were facing two serious testing challenges.

One was around environments – due to the nature of the enterprise it took around six months to have a real environment available to the team.

The second was a more persistent challenge – our identity provider was outdated. We were unable to authenticate through an API and the user would be redirected to a different domain whilst logging in through the UI.

All of these challenges did not play well with Cypress and end to end test automation. I was convinced there had to be a better way to provide feedback to the team.

Introducing Storybook.

Storybook is primarily a developer-centric tool for building reusable UI components. The idea being you provide UI components with the props they require to render under certain conditions.

Typically, you would only use for dumb components. To this day, I am yet to see anyone else mount the redux store and render store connected components.

Controversially to everyone online, I had the idea to run Storybook, mount the store and render page-level components and see if sagas would trigger to get asynchronous data from the microservices.

To my surprise, my hypothesis worked.

This led to my first working version of my vision of testing UI business logic at the component level without having dependencies on authenticating. All microservices were running locally and providing data to a redux mounted page component running inside Storybook.

At this point, I now had to clean state between each story page loading. I created a higher-order function that could be used as a storybook decorator to clean and set up state in-between a story loading.

In the `setupStory` function in the render, I wanted to replicate the root of my React application to make my testing as representative and thus making my testing as valuable as possible. I also wanted to have the ability to run functions before and after my component under test rendered to set up any required state.

It looked a bit like this:

taskManager.stories.js

```

import React from 'react';

import { storiesOf } from '@storybook/react';

import setupStory from '../helpers/setupStory';

import TasksContainer from '../../modules/task-manager/tasks-view/tasksViewContainer.jsx';




const preventScheduleReminder = () => localStorage.setItem('seenScheduleReminder', true);




storiesOf('Pages/TaskManager', module)

    .addDecorator(getStory =>

        setupStory(getStory, 'en',

        preventScheduleReminder))

    .add('is empty', () => <TasksContainer />));

```




setupStory.js

```

// all imports




export default (

  getStory,

  locale = 'de',

  setupStoryPreRender = () => {},

  setupStoryPostRender = () => {}

) => {

  store.dispatch(appActions.resetStore());

  localStorage.clear();




  localStorage.setItem('LOCALE', JSON.stringify({ locale, region: locale === 'de' ? 'DE' : 'GB' }));




  setupStoryPreRender();




  const loadComponent = () => {

    const story = getStory();

    setupStoryPostRender();

    return story;

  };

  return (

    <ThemeProvider theme={theme}>

      <StylesProvider>

        <Provider store={store}>

          <ConnectedRouter history={history}>

            <GlobalStyle />

            <Switch>

              <Suspense fallback="Loading">

                <InitialiseAppContainer />

                <Container>

                    <PageDiv>

                      <BodyDiv>{loadComponent()}</BodyDiv>

                    </PageDiv>

                </Container>

              </Suspense>

            </Switch>

          </ConnectedRouter>

        </Provider>

      </StylesProvider>

    </ThemeProvider>

  );

};

```


Feeling like I hadn’t quite gone far enough, I looked for ways to improve the solution further.

I felt having all the microservices running local was too clunky and not viable to do in CI, so I looked to implement stubs for each client call. Originally this was done with Sinon but eventually, I moved to StubbyJS as this was far more maintainable having a separate server opposed to overriding client-side function calls.

I was now in a position to load my page components inside Storybook, be decoupled from the microservices and start to exercise the business logic of the UI without having to navigate to the page under test.

Turbocharging automated check with Cypress

Cypress was an obvious choice to drive the browser and make assertions on the page behaviour. I wasn’t dealing with Iframes and no longer had multiple domains to authenticate against. Finally, Cypress enables grey box testing and can intercept HTTP requests from the application under test. This proved to be incredibly powerful.

What are you testing with this approach?

Well, the answer is a very deep isolated test of all the following:

– Actions, reducers, sagas and everything async all working.

– Making correct calls to our microservices

– The component rendering known stubbed state

– Component business logic is working

– Cypress server catching requests on submit actions and asserting on request bodies and headers.

Value

A couple of years ago I used to aim for a UI test to take around 20 seconds. However, it was not uncommon to see tests taking minutes if there was a lot of state to set up.

Compare this to 17 tests executing in 51 seconds. That works out to an average of 3 seconds per test 🏎.

Also, when these tests fail (which is rare due to the lack of interaction and moving parts) it is incredibly deterministic. You’re always on the page that failed, and the failure will always be the UI or the incorrect stub.

Finally, these tests were able to be independent of third parties and environments!

In the end, our team found these tests so valuable that we scraped our Enzyme tests altogether!

Horray!

Why not give it a go and let us know if you experience similar results.

More about the author:

Matthew Lowry is a full-stack engineer strongly orientated towards software delivery & quality. He currently works as a Principal Test Engineer at BP via ECS and often shares his experiences at community MeetUps. Most recently, Matthew appeared as a panellist for the Ask Me Anything Session on QA in DevOps, as well as a panellist for TAQfull’s virtual “Test Automation Practices from Innovative Brands” w/ Angie Jones event.

 

 

 

 

 

Found this interesting? Why not share it: