DEV Community

Cover image for Testing a React Context Provider
Manuel Artero Anguita
Manuel Artero Anguita

Posted on

Testing a React Context Provider

Quick context: I was asked to modify an existing Context Provider - 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...
    render(
      <MyContextProvider />
    );
Enter fullscreen mode Exit fullscreen mode
  • ...with child elements...
    render(
      <MyContextProvider>
        <Child />
      </MyContextProvider>
    );
Enter fullscreen mode Exit fullscreen mode
  • ...which may access the value provided by the Context Provider.
    render(
      <MyContextProvider>
        <Child /> /* test that Child may access the expected value */
      </MyContextProvider>,
    );
Enter fullscreen mode Exit fullscreen mode

Cool! we can turn that to more realistic code!

$> touch src/contexts/auth-context.test.js
Enter fullscreen mode Exit fullscreen mode
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()
  }
})
Enter fullscreen mode Exit fullscreen mode

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)

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;
Enter fullscreen mode Exit fullscreen mode

OOOOk, we do want to test that child elements may access these {user, isAdmin} values using React.useContext(). Something like:

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

Which seems to be the missing piece:

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
  }
})
Enter fullscreen mode Exit fullscreen mode

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).

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);
    });

  });
});
Enter fullscreen mode Exit fullscreen mode

Banner image: Education illustrations by Storyset

thanks for reading 💚.

Discussion (0)