Optimising testing strategy with React, Mobx, and Jest

Concept Reply GmbH
4 min readMar 27, 2023

Unit testing is an essential part of any application development process, as it helps ensure the quality and reliability of the code. When it comes to testing a React application that uses MobX for state management and React Context for providing stores to components, there are a few key strategies that can be employed to optimize the testing process.

In this article, we will discuss how we optimized our unit testing strategy for our React application that uses MobX, Jest, and React Testing Library.

First, let’s talk a bit about our structure…so that everything makes sense.

We’re providing stores via React context. Root store is the store holding reference to other stores. We created root store because entities in different stores became dependent as the complexity increases over time.

//creating a context
const StoreContext = React.createContext<RootStore | >(null);

const App: FC = (): ReactElement => {
return (
<StoreContext.Provider value={rootStores}>
{webRoutes}
</StoreContext.Provider>
);
};

Let’s create some helper functions-

//stores/index.ts
export const useStoresFromContext = (): RootStore => {
const rootStore = React.useContext(StoreContext);
if (!rootStore) {
throw new Error("rootStore should be provided in the context");
}
return rootStore;
};

//render method for tests
export const customRender = (
ui: ReactElement,
options?: Omit<RenderOptions, 'queries'>
) => render(ui, { wrapper: wrapper, ...options })

We created useStoresFromContext hook so we don’t end up having !null assertions in every component we are accessing the store.

//component/UserList.ts
const MyComponent = (): ReactElement => {
const { userStore } = useStoresFromContext();
//your component render code
}

The above method shows, how we are accessing the stores in a component.

And here’s what your test will look like:

describe("Sigin component",()=>{

let mockStore:UserStore;

beforeAll(()=>{
mockStore = new UserStore()
jest.spyOn(hooks, 'useStoresFromContext').mockImplementation(() => mockStore);
})

afterEach(()=>{
jest.restoreAllMocks()
})

test("Should render component when signin is disabled",async () => {
const {getByTestId} = customRender(<SignIn/>);
expect(getByTestId('buttton').closest('button')).toHaveAttribute('disabled');
})

test("Should render component when sigin is enabled",async () => {
mockStore.loginAllowed = true
jest.spyOn(hooks, 'useStoresFromContext').mockImplementation(() => mockStore);
const {getByTestId} = customRender(<SignIn/>);
expect(getByTestId('buttton').closest('button')).not.toHaveAttribute('disabled');
})
})

Enough of the structure, let’s come to the point.

Optimizing unit test strategy in React using react context, MobX, Jest, and React Testing Library involves a combination of best practices and specific strategies for each technology. Here are some examples and tips to follow:

  1. Using Setup and tear down methods efficiently to run actions before and after running tests. Configuring global variables or mock certain dependencies or services that your tests rely on.
  2. Mock all external dependencies by using the jest.mock() function to isolate the component from external dependencies and prevent any unwanted side effects from interfering with the test results.
  3. Spy on useStoreFromContext hook to use it in a test as same as your component is accessing the store. This will separate out the logic enclosed inside the hook.
  4. Using jest.spyOn method to spy on Mobx actions and track the state changes caused by the component under test. This helped us to ensure that the component was interacting with the store in the way that we expected it to, and also allowed us to assert that the correct actions were being dispatched.
  5. Mock external components so we don’t end up testing it multiple times and it will decrease the complexity of the test and make the test faster. Eg.
//component/UserList.ts
<Grid>
<Child/>
<h1>This is test</h1>
</Grid>

In the above code snippet, Child component needs to be mocked using jest.mock()

6. Following the testing philosophy of Many integration tests. No snapshot tests. Few unit tests. Few end to end tests.

7. A test case (tests inside describe block) can be employed to run enclosed tests sequentially to save reinitialising the same variables and re-mocking the dependencies or services.

Running tests in parallel can create issues with beforeEach and afterEach methods if they are not designed to handle concurrent access to shared resources. So in cases like a single component having a button enabled or disabled, it makes sense to render component in beforeAll or share some global variables but you need to ensure that those tests will be executed sequentially.

8. Using chained mock methods rather than creating a new mock object for different tests inside a test case (Again ensure if the test will be executed sequentially) . eg.

myMock.mockReturnValueOnce(10).mockReturnValueOnce('x').mockReturnValue(true);

9. Use resetAllMock, clearAllMock and restoreAllMock methods wisely. For ensuring your mock return value wouldn’t pollute other test cases use resetAllMock. For a mock function called n times use clearAllMock before your test, and for using original function, instead of mock implementation use restoreAllMock.

Conclusion

For the development of production grade code, testing is essential. In complex applications, the wrong testing strategy can lead to unstructured/unmaintainable code. When having larger applications, in addition, the execution time of tests increases. When you follow the strategy of continuous deployments, a 80 to 90 % test coverage, etc. a faster test execution has an impact on the efficiency for the developer.

By following the examples and tips presented in this article, We achieved 30% decrease in line of code we wrote in tests, and 40% decrease in the time to execute the tests in our react app.

--

--

Concept Reply GmbH

We advise and support our customers from Automotive, Manufacturing, Smart Infrastructure and other industries in all aspects related to Internet of Things (IoT)