Testing React Applications That Use Context Global State 
Tags: react, testing, globalstate, tutorial
Published: 2021-01-13 04:22:21

Isn't satisfying to see all your test are passing with all those green tags in your terminal. I want to share the way I test my React applications that use Context to manage global state. 

If you want to read how I use Context for global state management check out [this post](https://dev.to/rufataliy/react-global-state-with-usecontext-3d4n).

## General guidelines

>“Program to an interface, not an implementation.”
> [Design Patterns: Elements of Reusable Object Oriented Software](https://www.amazon.com/gp/product/B000SEIBB8?ie=UTF8&camp=213733&creative=393177&creativeASIN=B000SEIBB8&linkCode=shr&tag=eejs-20&linkId=CSQYBHTUP625XI4T)

While my internship at [Acerta](https://acerta.ai/) we needed to set up our testing environment and I was assigned to research current testing approaches. As a result, I found two main streams in testing React applications.

* Implementation oriented
* Result oriented

### Implementation oriented

If you are trying to test the internals of your component, like, is the state updating correctly, is the rendering happening, then you are doing implementation-oriented testing. The problem with this approach is that
* your implementation could change while still rendering the same interface and functionalities. It means that every time you make changes to your component you will need to adjust your tests as well, which is not ideal. 
* you will need to have more tests and mocks. 

Maybe you think that there are times that some logic needs to be thoroughly tested. In that case, your logic is too complex to be hosted by a React component. Create a custom hook and implement your logic there and import them to your component. This will make your component lighter and your testing easier. 

### Result oriented

Testing the result of your component is when you are testing your components closer to the way your users will interact with them. It means that you are not testing React rendered objects, but the real DOM. In this way, you will also test if your component is rendered at all, and if the elements that carry the main logic of the component are in the DOM, and if they behave correctly. The benefits of this approach is that
* you will have more robust tests that will be subject to less often changes
* you will test more with less code
* you will test in a way as your application will be interacted by users

## Mock API Requests

Enough of philosophy, let's get started with coding.

I usually use [msw](https://mswjs.io/) to mock my API requests. I strongly recommend it for your development environment. MSW is using service workers to intercept your API requests, which means that you don't change the way you fetch data. Just your API responses will come not from a server but predefined handlers. 

It is very useful while doing testing. Because you can use it in the browser and node environment.

## Mock Global State 

Now that we are good with API requests, let's tackle the global state.

As my components are using the global state directly, I need to mock it so that I can assert if the methods provided by the global state are being called properly.

I start by mocking my store object and assign [Jest](https://jestjs.io/en/) mock functions to all the methods that will be imported by the components I will test.

```
export const store: DefaultContext = {
  getRandomRecipes: jest.fn(),
  getRecipeById: jest.fn(),
  searchByName: jest.fn(),
  searchByCountry: jest.fn(),
  searchByCategory: jest.fn(),
  searchByIngredients: jest.fn(),
  resetReviewState: jest.fn(),
  setRecipeList: jest.fn(),
  loading: false,
  recipeList: null,
  reviewBarOpen: false,
  reviewLoading: false,
  reviewedRecipe: null,
};
```
The next step will be creating a mock `<StateProvider/>`. 
```
import React from "react";
import { Context } from "@/store/Context";
import { store } from "./mockStore";

export const StateProvider: React.FC = ({ children }) => {
  return <Context.Provider value={store}>{children}</Context.Provider>;
};
```
As you see here, I use the same `Context` element, but I pass my mock store to it as a value.

Alright, and now let's finally do some testing.

So the main technologies I use for testing are [Jest](https://jestjs.io/en/) and [Testing-library](https://testing-library.com/). 

Testing-library especially is created to encourage result-oriented testing. It provides you with utilities to render your component and deal with asynchronous methods in your components. It also provides `screen` API which represents the rendered element and selectors like `getByText`, `getByTestId` and etc. 

I want to specially talk about `getByTestId`. You can get elements from DOM in numerous ways and most cases, it can be valid. But if you think about making your tests more resilient to changes you don't want them to be dependant on tag decisions someone made or alt text or text content, and so on. These are the things that can be changed more often and sometimes you can't do even anything about it. That's why I recommend using the `data-testid` property on your HTML tags. 
* One benefit is that no matter what you render as long as you have `data-testid` on it your test will pass. 
* Another advantage is that it will communicate to other developers that this particular element is linked to some tests.

## Let's write some tests

I am going to test `<ByCategory/>` component from my [Recippy](https://recippy.rufat.tech/) project. This component is responsible for fetching categories from the server, displaying tabs with categories, and searching recipes by the selected category. It looks like this.

![bycategory](https://dev-to-uploads.s3.amazonaws.com/i/5ejdh8t5hpdfj7k9m9wz.gif)
 
So I will mimic this in my test. 

First, I start up my mock server. (msw)
```
describe("ByName", () => {
  server.listen();
```
Then I pick the method I want to run my assertion on. I use [Jest](https://jestjs.io/en/) `spyOn` method to reference the `searchByCategory`method in the global state.
```
  it("Should search by category", () => {
    const spy = jest.spyOn(mockStore, "searchByCategory");
```
Wrapping my element with my mock global state ...
``` 
    render(
        <StateProvider>
          <ByCategory />
        </StateProvider>
      );
```
Waiting for the loader to be unmounted. . .
```
    await waitForElementToBeRemoved(() => screen.getByTestId(LOADER));
```
Selecting a tab . . .
```
   const tab = screen.getByTestId(CATEGORY + index);
    
   expect(tab.textContent).toBe(categoryNames[index].strCategory);

   fireEvent.click(tab);
```
Submitting the search . . .
```
    const search_btn = screen.getByTestId(SEARCH_BTN);
    
    fireEvent.click(search_btn);
 ```
Asserting if my `searchByCategory` method is called properly . . .
``` 
   expect(spy).toBeCalledTimes(1);
  });
```
Finally, I close the server.
```
  server.close();
});
```

That's it. Nothing fancy. 

As you can see, I do the same thing as the user would do, but I test lots of things there. I test if I got a result from API if my loader was there and disappeared after the request is finalized if I had tabs to click, and finally if I can call API again to get my search result. 

As you can see this test covers most parts of the component. Of course, you can test use cases as well, but this is the case I am interested in.

Finally, remember that tests are dangerous if not implemented correctly.
 
You wanna know more about testing and Javascript development I would highly recommend following [Kent C. Dodds].

Thanks for reading.