DEV Community

Cover image for Testing a React Context Provider
Manuel Artero Anguita ๐ŸŸจ
Manuel Artero Anguita ๐ŸŸจ

Posted on

60 1 1 1

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 ๐Ÿ’š.

Image of Timescale

๐Ÿš€ pgai Vectorizer: SQLAlchemy and LiteLLM Make Vector Search Simple

We built pgai Vectorizer to simplify embedding management for AI applicationsโ€”without needing a separate database or complex infrastructure. Since launch, developers have created over 3,000 vectorizers on Timescale Cloud, with many more self-hosted.

Read more

Top comments (0)

Image of Docusign

๐Ÿ› ๏ธ Bring your solution into Docusign. Reach over 1.6M customers.

Docusign is now extensible. Overcome challenges with disconnected products and inaccessible data by bringing your solutions into Docusign and publishing to 1.6M customers in the App Center.

Learn more