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 />
);
- ...with child elements...
render(
<MyContextProvider>
<Child />
</MyContextProvider>
);
- ...which may access the value provided by the Context Provider.
render(
<MyContextProvider>
<Child /> /* test that Child may access the expected value */
</MyContextProvider>,
);
Cool! we can turn that to more realistic code!
$> touch src/contexts/auth-context.test.js
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)
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()
. Something like:
const TestingComponent = () => {
const { user, isAdmin } = useContext(AuthContext);
return (
<>
<p>{user?.name}</p>
<p>{isAdmin?.toString()}</p>
</>
);
};
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
}
})
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);
});
});
});
Banner image: Education illustrations by Storyset
thanks for reading π.
Top comments (0)