I am writing Jest/testing library tests.
Let's say we have a component called BenchmarksPage
.
Please look at the first line of its return statement.
import {
Box,
capitalize,
Container,
FormControl,
InputLabel,
MenuItem,
Select,
} from '@material-ui/core';
import { NextPage } from 'next';
import React, { useCallback, useEffect, useState } from 'react';
import { Helmet } from 'react-helmet-async';
import RequireScope from 'src/components/authentication/RequireScope';
import BenchmarkTable from 'src/components/benchmark/BenchmarkTable';
import DashboardLayout from 'src/components/dashboard/DashboardLayout';
import Heading from 'src/components/Heading';
import useSettings from 'src/hooks/useSettings';
import gtm from 'src/lib/gtm';
import { useDispatch, useSelector } from 'src/store';
import { getAllRollingTwelveCalcs } from 'src/store/rolling-twelve/rolling-twelve.thunk';
import { Timeframe, timeframeMap } from 'src/types/benchmark';
const BenchmarksPage: NextPage = () => {
const { settings } = useSettings();
const dispatch = useDispatch();
const [selectedTimeframe, setSelectedTimeframe] = useState<Timeframe>(
Timeframe.Monthly,
);
const company = useSelector((state) => state.company.current);
useEffect(() => {
gtm.push({ event: 'page_view' });
}, []);
useEffect(() => {
dispatch(getAllRollingTwelveCalcs());
}, [company]);
const handleTimeframeChange = useCallback(
(
event: React.ChangeEvent<{
name?: string;
value: Timeframe;
event: Event | React.SyntheticEvent<Element, Event>;
}>,
) => {
setSelectedTimeframe(event.target.value);
},
[],
);
return (
<RequireScope scopes={['query:benchmark-calcs']}>
<DashboardLayout>
<Helmet>
<title>Benchmarks</title>
</Helmet>
<Container maxWidth={settings.compact ? 'xl' : false}>
<Box
sx={{
display: 'flex',
alignItems: 'flex-end',
justifyContent: 'space-between',
mb: 4,
}}
>
<Heading>Benchmarks</Heading>
<FormControl sx={{ width: 300 }}>
<InputLabel>Timeframe</InputLabel>
<Select
sx={{ background: '#ffffff', maxWidth: 400 }}
value={selectedTimeframe}
label="Timeframe"
onChange={handleTimeframeChange}
>
{[...timeframeMap.keys()].map((timeframe) => (
<MenuItem key={timeframe} value={timeframe}>
{capitalize(timeframe)}
</MenuItem>
))}
</Select>
</FormControl>
</Box>
<BenchmarkTable timeframe={selectedTimeframe} />
</Container>
</DashboardLayout>
</RequireScope>
);
};
export default BenchmarksPage;
Note that its return value is wrapped with RequireScope
RequireScope
will only render its children when user is authenticated
RequireScope
:
import React, { useEffect, useState } from 'react';
import useAuth from 'src/hooks/useAuth';
export interface RequireScopeProps {
scopes: string[];
}
const RequireScope: React.FC<RequireScopeProps> = React.memo((props) => {
const { children, scopes } = props;
const { isInitialized, isAuthenticated, permissions } = useAuth();
const [isPermitted, setIsPermitted] = useState(false);
useEffect(() => {
if (process.env.NEXT_PUBLIC_IS_LOCAL) {
setIsPermitted(true);
}
}, []);
useEffect(() => {
if (isAuthenticated && isInitialized) {
(async () => {
const hasPermissions = scopes
.map((s) => {
return permissions.includes(s);
})
.filter(Boolean);
if (hasPermissions.length === scopes.length) {
setIsPermitted(true);
}
})();
}
}, [isAuthenticated, isInitialized, scopes, permissions]);
if (isPermitted) {
return <>{children}</>;
}
return null;
});
export default RequireScope;
We just need isPermitted
to be set to true
(useAuth uses JWT to sign in a user btw)
Now when I render the BenchmarksPage
using Testing Library's render
method,
it does not get rendered on the jsdom because the
'isPermitted'
is still false in RequireScope
.
So, in order to make isPermitted = true
I have set
NEXT_PUBLIC_IS_LOCAL=true
in .env.development
According to the first useEffect
in RequireScope, isPermitted
should be true
now.
Yet, the component is still not rendered, returning
<body></div></body>
This still means isPermitted is false.
What have I tried:
I have also tried using .env.local
I can ensure you that my setUps are all correct (jest.config, using MockProvider to wrap BenchmarksPage etc.)
Why isn't .env.development working, and making isPermitted = true
?
Everything is logically correct to my understanding.
EDIT: I have tried writing
NEXT_PUBLIC_IS_LOCAL=true
in .env.test file too
EDIT - ANSWER:
had to set
import { loadEnvConfig } from '@next/env';
export default async (): Promise<void> => {
loadEnvConfig(process.env.PWD);
};
in setupEnv.ts file.
And then in package.json, add globalSetup ( since I dont use jest.config.js -> eslint was messing with it along with tsconfig)
"jest": {
"moduleNameMapper": {
"^src/(.*)$": "<rootDir>/src/$1"
},
"testEnvironment": "jsdom",
"globalSetup": "<rootDir>/test/setupEnv.ts"
},
CodePudding user response:
If your environment variables work while running dev server, but won't work while testing, this is because Next doesn't set variables up for Jest while unit testing.
Firstly, create a .env.test
, setting up your environment variables that will be used for tests only.
Then, in order to set envs up in the test environment, you have to add this to your tests entry point:
// jest.config.js
module.exports = {
globalSetup: '<rootDir>/__test__/setupEnv.js'
}
// __test__/setupEnv.js
import { loadEnvConfig } from '@next/env'
export default async () => {
const projectDir = process.cwd()
loadEnvConfig(projectDir)
}
Please note that .env.local
is not loaded during tests