Fetching latest headlines…
Testing and Debugging Your Env Config
NORTH AMERICA
πŸ‡ΊπŸ‡Έ United Statesβ€’July 2, 2026

Testing and Debugging Your Env Config

1 views0 likes0 comments
Originally published byDev.to

Environment variables are global state. Testing them usually means mutating process.env and hoping you clean up after. CtroEnv gives you better tools.

Testing with objectSource

objectSource() wraps a plain object as an environment source β€” no globals involved:

import { describe, expect, it } from "vitest"
import { defineEnv, objectSource, string, number } from "@ctroenv/core"

function makeEnv(overrides: Record<string, string> = {}) {
  return defineEnv(
    {
      DATABASE_URL: string().url(),
      PORT: number().port().default(3000),
      JWT_SECRET: string().min(32).secret(),
    },
    { source: objectSource(overrides) },
  )
}

it("parses valid env vars", () => {
  const env = makeEnv({
    DATABASE_URL: "postgresql://localhost:5432/db",
    JWT_SECRET: "a".repeat(32),
  })
  expect(env.DATABASE_URL).toBe("postgresql://localhost:5432/db")
  expect(env.PORT).toBe(3000) // default
})

it("throws on missing required vars", () => {
  expect(() => makeEnv({ PORT: "4000" })).toThrow()
})

No beforeEach/afterEach cleanup. No global mutation. Each test gets a fresh environment.

Testing Validation Errors

Catch and inspect specific errors:

import { CtroEnvError } from "@ctroenv/core"

it("reports invalid URL", () => {
  try {
    makeEnv({
      DATABASE_URL: "not-a-url",
      JWT_SECRET: "a".repeat(32),
    })
  } catch (e) {
    expect(e).toBeInstanceOf(CtroEnvError)
    expect((e as CtroEnvError).errors[0].code).toBe("invalid_value")
    expect((e as CtroEnvError).errors[0].key).toBe("DATABASE_URL")
  }
})

it("collects all errors at once", () => {
  try {
    makeEnv({}) // both required vars missing
  } catch (e) {
    expect((e as CtroEnvError).errors).toHaveLength(2)
  }
})

No fix-one-find-another cycle. Every error surfaces in one throw.

Testing Secret Masking

it("masks secret values", () => {
  const env = makeEnv({
    DATABASE_URL: "postgresql://localhost:5432/db",
    JWT_SECRET: "x".repeat(32),
  })
  expect(env.JWT_SECRET).toBe("********")
  expect(env.meta.get("JWT_SECRET")).toBe("x".repeat(32))
})

it("supports custom mask", () => {
  const env = defineEnv(
    { KEY: string().secret() },
    { source: objectSource({ KEY: "value" }), maskWith: "***" },
  )
  expect(env.KEY).toBe("***")
})

Debugging with Error Codes

CtroEnv has four error codes. Each tells you exactly what went wrong:

Code Meaning Example
missing_required Variable not in source DATABASE_URL not set
type_mismatch Wrong JavaScript type String for number validator
invalid_value Failed refinement Invalid URL, port out of range
validation_failed Custom .validate() rejected API key format wrong

formatErrors

import { formatErrors } from "@ctroenv/core"

try {
  defineEnv(schema)
} catch (e) {
  if (e instanceof CtroEnvError) {
    process.stderr.write(formatErrors(e.errors))
    // Groups errors by type with colors
  }
}

CI/CD Integration

CtroEnv's CLI runs in CI without importing your schema β€” it parses config files directly.

Fast Key Check

npx ctroenv check --source .env.example

Compares keys only. Runs in under a second. Perfect for every PR.

Strict Validation

npx ctroenv check --source .env.staging --strict

Validates values against their validators β€” catches invalid URLs, out-of-range ports.

Unknown Key Warnings

npx ctroenv check --source .env --warn-unknown

Detects keys in your .env that aren't in the schema. Uses Levenshtein distance for suggestions:

⚠ Unknown key: "DATABSE_URL"
  β†’ Did you mean "DATABASE_URL"?

JSON Output

npx ctroenv check --source .env --json
{
  "clean": false,
  "summary": { "missing": 1, "unused": 0, "matched": 4 },
  "validationErrors": null
}

Pipe into Slack notifications, monitoring dashboards, or your own tooling.

GitHub Actions

name: Env Check
on: [pull_request]
jobs:
  check:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - run: npm ci
      - run: npx ctroenv check --source .env.example

Build-Time Validation

Framework plugins validate during the build:

// Vite
ctroenvPlugin({ schema: "./src/env.ts", failOnError: true })

// Next.js
export default withCtroEnv(schema, nextConfig)

If validation fails, the build exits with code 1. No deployment reaches production with a missing DATABASE_URL.

Links: GitHub Β· Docs Β· npm

Previous: Framework-Specific Env Patterns
Next: Shipping Reusable Env Schemas

Comments (0)

Sign in to join the discussion

Be the first to comment!