t3-env issues with Vitest test cases

It looks like client environments are not being read. No issues with the server envs.
No description
39 Replies
niels
niels13mo ago
It is certainly not empty in my .env
julius
julius13mo ago
Need more than that screenshot to help you…
niels
niels13mo ago
import { createEnv } from '@t3-oss/env-nextjs';
import { z } from 'zod';

export const env = createEnv({
server: {
NEXTAUTH_SECRET: z.string().min(1),
NEXTAUTH_URL: z.string().url(),
},
client: {
NEXT_PUBLIC_API_URL: z.string().url(),
},
runtimeEnv: {
// server
NEXTAUTH_SECRET: process.env.NEXTAUTH_SECRET,
NEXTAUTH_URL: process.env.NEXTAUTH_URL,

// client
NEXT_PUBLIC_API_URL: process.env.NEXT_PUBLIC_API_URL,
},
});
import { createEnv } from '@t3-oss/env-nextjs';
import { z } from 'zod';

export const env = createEnv({
server: {
NEXTAUTH_SECRET: z.string().min(1),
NEXTAUTH_URL: z.string().url(),
},
client: {
NEXT_PUBLIC_API_URL: z.string().url(),
},
runtimeEnv: {
// server
NEXTAUTH_SECRET: process.env.NEXTAUTH_SECRET,
NEXTAUTH_URL: process.env.NEXTAUTH_URL,

// client
NEXT_PUBLIC_API_URL: process.env.NEXT_PUBLIC_API_URL,
},
});
my env.mjs
import crypto from 'crypto';
import { describe, expect, it } from 'vitest';
import { z } from 'zod';
import { AuthResponseSchema } from '../auth';

describe('AuthResponseSchema', () => {
it('valid data passes', () => {
const data = {
id: crypto.randomUUID(),
token: 'validtoken',
};

expect(AuthResponseSchema.parse(data)).toEqual(data);
});

it('invalid data throws error', () => {
const data = {
id: 'invaliduuid',
token: '',
};
expect(() => AuthResponseSchema.parse(data)).toThrowError(z.ZodError);
});
});
import crypto from 'crypto';
import { describe, expect, it } from 'vitest';
import { z } from 'zod';
import { AuthResponseSchema } from '../auth';

describe('AuthResponseSchema', () => {
it('valid data passes', () => {
const data = {
id: crypto.randomUUID(),
token: 'validtoken',
};

expect(AuthResponseSchema.parse(data)).toEqual(data);
});

it('invalid data throws error', () => {
const data = {
id: 'invaliduuid',
token: '',
};
expect(() => AuthResponseSchema.parse(data)).toThrowError(z.ZodError);
});
});
the test case
❯ onValidationError node_modules/.pnpm/@t3-oss+env-nextjs@0.3.1_typescript@5.0.4_zod@3.21.4/node_modules/@t3-oss/core/index.ts:128:13
❯ g node_modules/.pnpm/@t3-oss+env-nextjs@0.3.1_typescript@5.0.4_zod@3.21.4/node_modules/@t3-oss/core/index.ts:140:12
❯ Module.C node_modules/.pnpm/@t3-oss+env-nextjs@0.3.1_typescript@5.0.4_zod@3.21.4/node_modules/@t3-oss/env-nextjs/index.ts:28:10
❯ src/env.mjs:4:20
2| import { z } from 'zod';
3|
4| export const env = createEnv({
| ^
5| server: {
6| NEXTAUTH_SECRET: z.string().min(1),
❯ src/lib/api.ts:1:31
❯ onValidationError node_modules/.pnpm/@t3-oss+env-nextjs@0.3.1_typescript@5.0.4_zod@3.21.4/node_modules/@t3-oss/core/index.ts:128:13
❯ g node_modules/.pnpm/@t3-oss+env-nextjs@0.3.1_typescript@5.0.4_zod@3.21.4/node_modules/@t3-oss/core/index.ts:140:12
❯ Module.C node_modules/.pnpm/@t3-oss+env-nextjs@0.3.1_typescript@5.0.4_zod@3.21.4/node_modules/@t3-oss/env-nextjs/index.ts:28:10
❯ src/env.mjs:4:20
2| import { z } from 'zod';
3|
4| export const env = createEnv({
| ^
5| server: {
6| NEXTAUTH_SECRET: z.string().min(1),
❯ src/lib/api.ts:1:31
trace
import { env } from '@/env.mjs';
import axios, { type AxiosInstance } from 'axios';

/**
* An Axios instance with its base URL set to the API URL from the Sales middleware.
* @see https://code.notive.io/camera-tweedehands/sales-middleware-django
*
* @returns {AxiosInstance} An Axios instance.
*/
const api: AxiosInstance = axios.create({
baseURL: env.NEXT_PUBLIC_API_URL,
});

export default api;
import { env } from '@/env.mjs';
import axios, { type AxiosInstance } from 'axios';

/**
* An Axios instance with its base URL set to the API URL from the Sales middleware.
* @see https://code.notive.io/camera-tweedehands/sales-middleware-django
*
* @returns {AxiosInstance} An Axios instance.
*/
const api: AxiosInstance = axios.create({
baseURL: env.NEXT_PUBLIC_API_URL,
});

export default api;
api.ts
# Since the ".env" file is gitignored, you can use the ".env.example" file to
# build a new ".env" file when you clone the repo. Keep this file up-to-date
# when you add new variables to `.env`.

# This file will be committed to version control, so make sure not to have any
# secrets in it. If you are cloning this repo, create a copy of this file named
# ".env" and populate it with your secrets.

# When adding additional environment variables, the schema in "/src/env.mjs"
# should be updated accordingly.

# API
NEXT_PUBLIC_API_URL="http://localhost:8000/api"

# Next Auth
# You can generate a new secret on the command line with:
# openssl rand -base64 32
# https://next-auth.js.org/configuration/options#secret
NEXTAUTH_SECRET="UQzCUvyvDXxYKKVAPjuNy1D9td6G4b9XUmmqlAAzucE="
NEXTAUTH_URL="http://localhost:3000"
# Since the ".env" file is gitignored, you can use the ".env.example" file to
# build a new ".env" file when you clone the repo. Keep this file up-to-date
# when you add new variables to `.env`.

# This file will be committed to version control, so make sure not to have any
# secrets in it. If you are cloning this repo, create a copy of this file named
# ".env" and populate it with your secrets.

# When adding additional environment variables, the schema in "/src/env.mjs"
# should be updated accordingly.

# API
NEXT_PUBLIC_API_URL="http://localhost:8000/api"

# Next Auth
# You can generate a new secret on the command line with:
# openssl rand -base64 32
# https://next-auth.js.org/configuration/options#secret
NEXTAUTH_SECRET="UQzCUvyvDXxYKKVAPjuNy1D9td6G4b9XUmmqlAAzucE="
NEXTAUTH_URL="http://localhost:3000"
and the env itself I'm on next 13.4.3 and @t3-oss/env-nextjs 0.3.1 I am unable to share the repo sadly as it is a confidential work repo Also running pnpm build works fine too weirdly enough
niels
niels13mo ago
Also in dev, I have access to all env values
No description
niels
niels13mo ago
Seems like the test is the only issue
import react from '@vitejs/plugin-react';
import tsconfigPaths from 'vite-tsconfig-paths';
import { defineConfig } from 'vitest/config';

// https://vitejs.dev/config/
export default defineConfig({
plugins: [tsconfigPaths(), react()],
test: {
environment: 'jsdom',
},
});
import react from '@vitejs/plugin-react';
import tsconfigPaths from 'vite-tsconfig-paths';
import { defineConfig } from 'vitest/config';

// https://vitejs.dev/config/
export default defineConfig({
plugins: [tsconfigPaths(), react()],
test: {
environment: 'jsdom',
},
});
maybe my config is wrong 🤔 With the environment set to node, it doesnt see any envs
❌ Invalid environment variables: {
NEXTAUTH_SECRET: [ 'Required' ],
NEXTAUTH_URL: [ 'Required' ],
NEXT_PUBLIC_API_URL: [ 'Required' ]
}
❌ Invalid environment variables: {
NEXTAUTH_SECRET: [ 'Required' ],
NEXTAUTH_URL: [ 'Required' ],
NEXT_PUBLIC_API_URL: [ 'Required' ]
}
whereas with jsdom only the NEXT_PUBLIC_API_URL is invalid
Finn
Finn13mo ago
You have to turn of env validation if your not Gona give the env vars Create env takes a option for that
niels
niels13mo ago
but the env vars are in my .env here so im not sure why pnpm test doesnt see that
Finn
Finn13mo ago
skipValidation: !!process.env.SKIP_ENV_VALIDATION, Oh true. But I guess you won't always have them when you run your tests You could make it somthing like skipValidation: !!process.env.SKIP_ENV_VALIDATION || !!import.meta.vitetest
niels
niels13mo ago
whats || !!import.meta.vitetest
Finn
Finn13mo ago
Skips validation when it's run via vitest Or atleast I'm hoping
niels
niels13mo ago
im not sure if thats the right approach cause the env is still empty { NEXTAUTH_SECRET: undefined, NEXTAUTH_URL: undefined, NEXT_PUBLIC_API_URL: undefined } this is when i log the full env in some test
Finn
Finn13mo ago
Ahhh yeah it won't be then
niels
niels13mo ago
Hope Julius has any ideas
Finn
Finn13mo ago
Have you got a minimal repro?
niels
niels13mo ago
What do you mean?
Finn
Finn13mo ago
A repo that contains just the code needed to reproduce the issue
niels
niels13mo ago
I dont have the time to set that up right now, im at work haha does it have to do with the VITE_ prefix maybe looks like it....
{
NEXTAUTH_SECRET: undefined,
NEXTAUTH_URL: undefined,
NEXT_PUBLIC_API_URL: undefined,
VITE_TEST_ENV: 'test'
}
{
NEXTAUTH_SECRET: undefined,
NEXTAUTH_URL: undefined,
NEXT_PUBLIC_API_URL: undefined,
VITE_TEST_ENV: 'test'
}
damn thats inconvenient
Finn
Finn13mo ago
Fairs Hmmm is there anything in Thier docs about env vars?
niels
niels13mo ago
not really i believe might have to add dotenv package
Finn
Finn13mo ago
Oh yeah 🤦‍♂️ . Your using next. Should have seen that sooner
niels
niels13mo ago
there's gotta be some workaround for this no? @marminge have you come across this issue whilst building the t3-complete repo that also uses Vitest?
Finn
Finn13mo ago
Don't think complete does unit testing Can you call dotenv from your vite config? That might work
niels
niels13mo ago
It does with Vitest and Playwright not sure how dotenv works, gotta figure that out
Finn
Finn13mo ago
Those are e2e tests not unit test. I asume it brings up the next app. Hence no env errors require("dotenv").config()
niels
niels13mo ago
rest is automatic? no extra config files
niels
niels13mo ago
damn that actually seems to work
No description
Finn
Finn13mo ago
Ya
niels
niels13mo ago
without needing VITE_
Finn
Finn13mo ago
That's all next does under the hood
niels
niels13mo ago
only gotta fix these now..
No description
Finn
Finn13mo ago
Quick fix => ignore
niels
niels13mo ago
theres no jsdoc @type for that?
Finn
Finn13mo ago
Dunno. Don't need it tho
niels
niels13mo ago
welp it works for now. thanks for your effort. eventual fix was adding this to vitest.config.ts
/**
* Mandatory for working with environment variables in Vitest.
* This is a workaround for the fact that Vitest does not include environment variables other than ones prefixed with `VITE_`.
*/
// eslint-disable-next-line
require('dotenv').config();
/**
* Mandatory for working with environment variables in Vitest.
* This is a workaround for the fact that Vitest does not include environment variables other than ones prefixed with `VITE_`.
*/
// eslint-disable-next-line
require('dotenv').config();
No description
niels
niels13mo ago
@Finn ( CLOwn ) thanks for your time and effort
Finn
Finn13mo ago
Noice nw
stuartrobinson
stuartrobinson10mo ago
I couldn't get this to work. Instead I added the following line to defineConfig:
process.env = loadEnv(mode, process.cwd(), '')
process.env = loadEnv(mode, process.cwd(), '')
Full code here:
import { defineConfig } from 'vitest/config'
import { loadEnv } from 'vite'

export default defineConfig(({ mode }) => {
// By default this plugin will only load variables that start with VITE_*
// To fix this we can use the loadEnv function from Vite
// We set the third parameter to '' to load all env regardless of the `VITE_` prefix.
process.env = loadEnv(mode, process.cwd(), '')
return {
test: {
globals: true,
includeSource: ['lib/**/*.{js,ts}', 'utils/**/*.{js,ts}'],
environment: 'happy-dom',
alias: {
'@/': new URL('./', import.meta.url).pathname,
},
},
}
})
import { defineConfig } from 'vitest/config'
import { loadEnv } from 'vite'

export default defineConfig(({ mode }) => {
// By default this plugin will only load variables that start with VITE_*
// To fix this we can use the loadEnv function from Vite
// We set the third parameter to '' to load all env regardless of the `VITE_` prefix.
process.env = loadEnv(mode, process.cwd(), '')
return {
test: {
globals: true,
includeSource: ['lib/**/*.{js,ts}', 'utils/**/*.{js,ts}'],
environment: 'happy-dom',
alias: {
'@/': new URL('./', import.meta.url).pathname,
},
},
}
})
And also specify the mode when running vitest:
vitest --mode development
vitest --mode development
BigLung
BigLung10mo ago
@stuartrobinson what file is that config in? Is that just your vitest.config.ts?
stuartrobinson
stuartrobinson10mo ago
That’s right