Local vs CI Test Consistency Guide

This guide helps ensure your tests pass both locally and in CI/Docker environments.

Common Issues and Solutions

1. Node Version Mismatch

Issue: Ensure local and Docker use the same Node version (Node 18) Solution:

# Use the project's Node version (defined in .nvmrc)
nvm use
# If not installed:
nvm install 18
nvm use 18

2. TypeScript Strictness

Issue: TypeScript checks ALL files in Docker (including tests) Solution: Always run TypeScript checks before pushing:

npm run typecheck

3. Test Environment Differences

Issue: CI runs tests differently than local Solution: Test in CI mode locally:

CI=true npm run test:run

4. Module Resolution

Issue: Dynamic require() with path aliases fails in Docker Solution: Don’t use require() with TypeScript path aliases in tests:

// ❌ BAD - Will fail in Docker
const module = require('@/utils/someModule')

// ✅ GOOD - Use imports
import { someFunction } from '@/utils/someModule'

Before Every Commit

# Quick quality check (lint + typecheck)
npm run quality:check:fast

Before Every Push

# Full quality check (matches Docker build)
npm run quality:check

If Tests Fail in CI

  1. Check Node version:
    node --version  # Should be v18.x.x
    nvm use
    
  2. Run CI mode locally:
    CI=true npm run test:run
    
  3. Check TypeScript errors:
    npm run typecheck
    
  4. Run full Docker check:
    npm run docker:check
    

TypeScript Test Guidelines

Always Type Your Test Data

// ❌ BAD - Missing types
const mockData = {
  accessControl: {},  // TypeScript error in strict mode
  selectedItems: undefined
}

// ✅ GOOD - Properly typed
import type { AccessControl } from '@/lib/firebase'
import type { SelectedItem } from '@/types/file-browser'

const accessControl: AccessControl = { type: 'private' }
const selectedItems: SelectedItem[] = []

Handle Possibly Undefined Values

// ❌ BAD - Can be undefined
const body = JSON.parse(fetchCall[1].body as string)

// ✅ GOOD - Null-safe
const body = JSON.parse(fetchCall[1]?.body as string)

Don’t Add Properties That Don’t Exist in Types

// ❌ BAD - SelectedItem doesn't have 'size'
const item: SelectedItem = {
  path: '/file.txt',
  name: 'file.txt',
  type: 'file',
  size: 1024  // TypeScript error
}

// ✅ GOOD - Only use defined properties
const item: SelectedItem = {
  path: '/file.txt',
  name: 'file.txt',
  type: 'file'
}

Environment Variables

Local Development

  • Uses .env.local
  • Firebase emulators by default

CI/Docker

  • Uses environment variables from Cloud Build
  • No Firebase emulators
  • CI=true changes test behavior

Quick Reference

Command Purpose When to Use
npm run lint Check code style Before commits
npm run typecheck Check TypeScript types Before commits
npm run test:run Run tests once Before commits
CI=true npm run test:run Run tests in CI mode Before pushes
npm run quality:check:fast Quick lint + typecheck Before commits
npm run quality:check Full CI simulation Before pushes
npm run docker:check Complete Docker build test When tests fail in CI

Debugging CI Failures

  1. Read the error carefully - CI errors often show the exact TypeScript issue
  2. Check the build step - Is it failing at lint, typecheck, test, or build?
  3. Reproduce locally - Use the exact command that failed
  4. Fix types, not tests - Don’t modify tests to pass; fix the underlying type issues

Prevention

  1. Use the correct Node version - Check .nvmrc
  2. Import types properly - Always import types you use in tests
  3. Run quality checks - Use npm run quality:check before pushing
  4. Keep dependencies updated - Run npm install after pulling changes