Testing a React Context Provider
Tags: react, javascript, testing, tutorial
Published: 2022-02-20 09:55:42

Quick context: I was asked to modify an existing [Context Provider](https://reactjs.org/docs/context.html#contextprovider) - named `<AuthProvider />` - and its associated context, `AuthContext`. 
No tests. So, decided to cover the thing before touching anything.

And I was like _how do I test a Context Provider_ 🤔?

---

Let's do some white-boarding: 

* A Context provider is a component...
  ```jsx
    render(
      <MyContextProvider />
    );
  ```
* ...with child elements...
  ```jsx
    render(
      <MyContextProvider>
        <Child />
      </MyContextProvider>
    );
  ```
* ...which may access the value provided by the Context Provider.
  ```jsx
    render(
      <MyContextProvider>
        <Child /> /* test that Child may access the expected value */
      </MyContextProvider>,
    );
  ```

Cool! we can turn that to more realistic code!

```bash
$> touch src/contexts/auth-context.test.js
```

```jsx
import { render } from '@testing-library/react';
import AuthContext, { AuthProvider } from './auth-context';

describe('<AuthProvider />', () => {
  test('provides expected AuthContext obj to child elements', () => {
    render(
      <AuthContext>
        < ?? />
      </AuthContext>,
    );
    // expect()
  }
})
```

We're making progresses here 👩‍⚕️.

---

Next. These are the context and provider component we're to test (I've omitted the specifics of our project) 

```tsx
const AuthContext = createContext<{
  user?: AuthUser | undefined;
  isAdmin?: boolean;
}>({});

export function AuthProvider({ children }: Props): JSX.Element {
  const [user, setUser] = useState<AuthUser | undefined>();

  useEffect(() => {
    /* API calls */ 
    setUser( ... )
  }, []);

  const isAdmin = () => /* some logic */

  return (
    <AuthContext.Provider value={{ user, isAdmin: isAdmin() }}>
      {children}
    </AuthContext.Provider>
  );
}

export default AuthContext;
```

OOOOk, we do want to test that child elements may access these `{user, isAdmin}` values using [`React.useContext()`](https://reactjs.org/docs/hooks-reference.html#usecontext). Something like:

```jsx
const TestingComponent = () => {
  const { user, isAdmin } = useContext(AuthContext);
  return (
    <>
      <p>{user?.name}</p>
      <p>{isAdmin?.toString()}</p>
    </>
  );
};
```

Which seems to be the missing piece:

```diff
import { render } from '@testing-library/react';
import AuthContext, { AuthProvider } from './auth-context';

+
+ const TestingComponent = () => {
+  const { user, isAdmin } = useContext(AuthContext);
+  return (
+    <>
+      <p>{user?.name}</p>
+      <p>{isAdmin?.toString()}</p>
+    </>
+  );
+};
+
describe('<AuthProvider />', () => {
  test('provides expected AuthContext to child elements', () => {
    render(
      <AuthContext>
-        < ?? />
+        <TestingComponent />
      </AuthContext>,
    );
-    // expect()
+    // expected name
+    // expected isAdmin
  }
})
```

---

We have now all the pieces; `AuthContext` gets the user making some HTTP requests, then exposes the user + one processed property (`isAdmin`). 

Our final unit test for this **React context provider** looks as following. (I've omitted lines related to mocking the API, those will depend on which request library you're using).

```jsx
import { render } from '@testing-library/react';
import AuthContext, { AuthProvider } from './auth-context';

const TestingComponent = () => {
  const { user, isAdmin } = useContext(AuthContext);
  return (
    <>
      <p>{user?.name}</p>
      <p>{isAdmin?.toString()}</p>
    </>
  );
};

const regularUser = {
  name: 'Jhon Doe',
  /* fields that resolve as not admin */
};

const adminUser = {
  name: 'Jane Doe',
  /* fields that resolve as admin user */
};

describe('<AuthProvider />', () => {
  test('provides expected AuthContext to child elements', () => {
   [
     {
       scenario: 'regular user',
       user: regularUser,
       expectedName: 'Jhon Doe',
       expectedAdmin: 'false',
     },
     {
       scenario: 'admin user',
       user: adminUser,
       expectedName: 'Jane Doe',
       expectedAdmin: 'true',
     }
   ].forEach(({ scenario, user, expectedName, expectedAdmin }) => {

    test(scenario, () => {
      /* mock the HTTP request */

      const { getByTestId } = render(
        <AuthProvider>
          <TestingComponent />
        </AuthProvider>,
      );

      const userName = getByTestId('user-name');
      const isAdminValue = getByTestId('is-admin');
      expect(userName.textContent).toEqual(expectedName);
      expect(isAdminValue.textContent).toEqual(expectedAdmin);
    });

  });
});
```

---

> Banner image: <a href="https://storyset.com/education">Education illustrations by Storyset</a>

thanks for reading 💚.