Don’t let unit testing test you

Alexis Marroquin
5 min readJun 24, 2021

Some context

I worked with a non-profit organization named Human Rights First on their Blue Witness project. Human Rights First is an independent organization that aims to protect human rights by holding authority figures accountable. The motivation behind Blue Witness is to provide the public access to their database of crowdsourced reports of police use of force at protests. The reports come from multiple sources, including Twitter, and go through an approval process. After the report is confirmed to be a legitimate case, it gets posted to the database.

The project captures the volume and frequency of cases by displaying a map of the US with clusters around hotspots. Users can also search and filter by location, date, or tags. I worked, as a frontend developer, alongside a team of frontend, backend developers and data scientists.

Great, but what’s the issue?

The problem focused on while working on this product was unit testing. There were some tests that had previously been implemented in the frontend codebase, but they were yet to be fleshed out. The tests did not provide assurance as to how the component acted when dealing with edge cases.

Instead, most tests only asserted that a component rendered to the DOM (screen). My solution, was to create a more robust testing suite, expanding test coverage and bringing confidence in the product up by testing for specific elements.

Resources are abundant, yet quality is not

Unit testing is one of three methods used in development. The purpose of testing is to provide developers confidence that the code pushed to the production version of the application can handle general and edge cases. For the most part, unit tests are concerned not with the app as a whole, but individual “units” ( think react components ). An example assertion a unit test can make is, when the button is clicked a pop-up message renders to the DOM.

Two colleagues and I estimated that we could get most tests written by a week. We spent a couple of hours on rigorous research and consulting with a few mentors and even previous collaborators. It became apparent that there was some boilerplate to setup before we could implement tests more robust tests.

Building a more robust testing suite

My task involved helping set up and write tests for the incidents page as a team. The tests were using a custom render function made from the render function built into the @testing-library/react NPM package. When testing the components you must render them to the screen first, in order to manipulate and test it. This custom function wrapped the entire component being tested in an Okta authorization component. Okta is a service that securely authorizes users. The custom render function prevents a security error due to components being rendered without authorization from Okta.

Another challenge, was working with a new ( to me ) component library called AntDesign. This library provides pre-made components for more efficient development and consistent styling. Although I do have some experience with libraries such as Material-UI and styled-components, AntDesign worked slightly differently. The AntDesign components were a bit difficult to test, due to the nature of how they render, but after speaking with my team members we began to understand why the tests were so simple in the beginning.

We decided to continue using Jest and the custom functions provided for the tests to run without any errors. Next, we realized that we needed a mock redux store in order to confirm that the varying slices of state displayed as expected. To create a mock store, we used the configureMockStore method from the redux-mock-store library and sample data representing the incident reports displayed.

import React from 'react';
import { render, act } from '@testing-library/react';

import 'jest-canvas-mock';
// Provider components
import OktaWrapper from '../components/OktaWrapper';
import { Provider } from 'react-redux';
import { MemoryRouter } from 'react-router-dom';
// Mock Redux store
import configureMockStore from 'redux-mock-store';
import { mockInitialState } from './mockStoreData';
/* Provides a custom render function for tests that makes Redux, React Router and Okta available to components */// Configure Mock Store without middleware
const middlewares = [];
const mockStore = configureMockStore(middlewares);
// Create mock store with sample data.
const store = mockStore(() => mockInitialState);
function Wrapper(props) {
return (
<Provider store={store}>
<MemoryRouter>
<OktaWrapper>{props.children}</OktaWrapper>
</MemoryRouter>
</Provider>
);
};
const wrapRender = async (component, options) => {
let renderReturned;

/* wraps render function in async act() callback to prevent Okta errors */
await act(async () => {
renderReturned = await render(
component,
{ wrapper: Wrapper, ...options }
);

});

return renderReturned;

/* act() requires no return value, so return what render would normally return */
};

The Result

The mock redux store was fed sample incident reports and wrapped the component being tested via the custom render function that had previously been setup only to provide Okta authorization. A few features that we tested were:

  • filtering by rank only displays specified rank
  • filter by rank removes extra pages of results
  • component displays unfiltered incidents on page load

A challenge was dealing with AntDesign components and not fully understanding the nature of how the components rendered at first. I was attempting to select an element on the DOM by label and the test kept failing due to the DOM element not found. It turns out the AntDesign components, in this case, did not render immediately.

To select an element in the DOM, I used the screen object from @testing-library/react. screen has various selection methods that work differently including: .getBy, .findBy and .queryBy . After unsuccessfully selecting the elements synchronously using screen.getByLabel(), I attempted to use the screen.findByLabel() method. This method required the action to be “awaited” using async/await . Once this method was used, the elements began displaying in the tests. Once this was discovered and the mock store was set up, all that was left was implementation of as many tests as possible.

I foresee future teams being challenged by the utilization of the wrapper providing the mock store and Okta authorization setup. Also, rendering and selecting AntDesign components can be difficult without properly rendering them. The tests require a couple of wrappers in order to work making them more complex as is.

/* Example of filter by rank test */test('Filter by rank displays events with specific rank',async ()=>{  // render the component to the DOM
await render(<Incidents />);

// get rank filter button
const rankFilter = screen.getByLabelText(/rank/i);
// click rank filter
userEvent.click(rankFilter);
// select rank 2
const rank2 = screen.getByText(/rank: 2/i);
// click rank 2 async
await act(() => {
userEvent.click(rank2); }); // get rank 2 cases render
const rank2s = screen.getAllByText(/rank 2/i);
const rank1s = screen.queryByText(/rank 1/i);

// sample data contains more rank 2s
expect(rank2s.length).toBeGreaterThanOrEqual(2);
expect(rank1s).toBe(null);
});

Some takeaways

Collaborating on Human Rights First’s Blue Witness project taught me a few key takeaways. Firstly, effective communication is vital for any project to work out. My team members and I consistently met on voice calls and shared any questions/answers that needed to be addressed. One piece of feedback I received early on was to stay determined and confident, despite having a short amount of time to work on this project. I was demotivated at times due to unexpected circumstances such as unfamiliar testing boilerplate. As I worked on the project and gained a better understanding of it as a whole, I became more confident in my ability to jump into a project with a team and work together toward a unified vision.

--

--