Skip to content

tsoa controllers crash in Vitest CI (Node 20, ESM) with Class extends value undefined while working locally #1819

@Teyk0o

Description

@Teyk0o

Hi 👋,

I’m running into an issue with tsoa when testing an Express app using Vitest in CI (GitHub Actions).
Everything works fine locally, but the same tests fail consistently in CI.

This looks like a runtime/import issue related to how tsoa-generated code behaves under ESM + Vitest + Node 20.


Context

  • Node.js: 20

  • Test runner: Vitest

  • Framework: Express

  • tsoa version: 7.x (alpha)

  • Environment:

    • ✅ Works locally
    • ❌ Fails in GitHub Actions (Ubuntu)

The API app is structured so that the Express app is created in a factory function (createApp) and then tested with supertest.


Test code

import { describe, it, expect } from 'vitest';
import request from 'supertest';
import { createApp } from '../src/app';

describe('API health', () => {
  it('responds to /v1/hello', async () => {
    const app = createApp();

    const res = await request(app).get('/v1/hello');
    expect(res.status).toBe(200);
    expect(res.body).toHaveProperty('message');
  });
});

CI configuration

name: CI

on:
  pull_request:
  push:
    branches: [main]

jobs:
  test:
    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@v4

      - uses: pnpm/action-setup@v4
        with:
          version: 10.25.0

      - uses: actions/setup-node@v4
        with:
          node-version: 20
          cache: pnpm

      - name: Install dependencies
        run: pnpm install

      - name: Generate Prisma client
        env:
          DATABASE_URL: "postgresql://dummy:dummy@localhost:5432/dummy"
        run: pnpm --filter @repo/db prisma generate

      - name: Generate API client (Orval)
        run: pnpm generate:api-client

      - name: Run tests (no DB)
        run: pnpm test

Error observed in CI

TypeError: Class extends value undefined is not a constructor or null

❯ src/controllers/HelloController.ts:4:38
  @Route('v1/hello')
  export class HelloController extends Controller {
                                       ^

❯ src/generated/routes.ts:7:1
❯ src/app.ts:6:1

Full log excerpt:

FAIL  tests/health.test.ts
TypeError: Class extends value undefined is not a constructor or null
❯ src/controllers/HelloController.ts:4:38
❯ src/generated/routes.ts:7:1
❯ src/app.ts:6:1

Notes / Observations

  • reflect-metadata is already imported before any controller is loaded
  • Controllers correctly import Controller from tsoa
  • The error only happens in CI (fresh environment)
  • The failure happens when importing the tsoa-generated routes
  • It looks like Controller is undefined at runtime in this context

This suggests a possible incompatibility between:

  • tsoa generated code
  • ESM execution
  • Vitest runtime (as opposed to ts-node / tsx)

Question

Is this a known limitation or incompatibility when using tsoa with Vitest + ESM + Node 20?

Is there a recommended way to test an Express app using tsoa-generated routes in a pure ESM / CI environment?

Any guidance would be appreciated. Thanks!

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions