Home > Back-end >  Why isn't .env.development working in my Next.js app?
Why isn't .env.development working in my Next.js app?

Time:04-10

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

  • Related