Skip to content

Testing

venpm has 224 tests across three layers, all using vitest.

Running Tests

bash
npm test                  # run all tests once
npm run test:watch        # watch mode
npm run test:coverage     # with coverage report

Test Layers

Unit Tests (tests/unit/)

Test individual core modules in isolation. Each core module has a corresponding test file. IOContext interfaces are mocked — no filesystem, network, or process spawning.

Test FileModule Under Test
builder.test.tscore/builder.ts
cache.test.tscore/cache.ts
config.test.tscore/config.ts
detect.test.tscore/detect.ts
fetcher.test.tscore/fetcher.ts
json.test.tscore/json.ts
lockfile.test.tscore/lockfile.ts
log.test.tscore/log.ts
paths.test.tscore/paths.ts
prompt.test.tscore/prompt.ts
registry.test.tscore/registry.ts
resolver.test.tscore/resolver.ts
schema.test.tscore/schema.ts
types.test.tscore/types.ts

Integration Tests (tests/integration/)

Test full command workflows with mocked IOContext. These call the execute*() functions directly (not through commander) with a mock context that captures all I/O.

Test FileWorkflow
install.test.tsFull install flow including dependency resolution
uninstall.test.tsUninstall with reverse dependency warnings
create.test.tsRepo and plugin scaffolding
json-commands.test.ts--json output for list, search, info

E2E Tests (tests/e2e/)

Run the compiled CLI as a subprocess against real temp directories. Tests use execFile("node", [CLI_PATH, ...args]) with a temporary XDG_CONFIG_HOME.

Test FileCoverage
cli.test.tsFull CLI: doctor, config, repo, create, validate

Writing Tests

Mocking IOContext

Create a mock context with stubs for every interface:

typescript
import { IOContext } from "../../src/core/types.js";
import { vi } from "vitest";

function createMockContext(): IOContext {
    return {
        fs: {
            readFile: vi.fn(),
            writeFile: vi.fn(),
            exists: vi.fn().mockResolvedValue(false),
            mkdir: vi.fn(),
            rm: vi.fn(),
            symlink: vi.fn(),
            readlink: vi.fn(),
            readdir: vi.fn().mockResolvedValue([]),
            stat: vi.fn(),
            lstat: vi.fn(),
            copyDir: vi.fn(),
        },
        http: {
            fetch: vi.fn(),
        },
        git: {
            available: vi.fn().mockResolvedValue(true),
            clone: vi.fn(),
            pull: vi.fn(),
            revParse: vi.fn(),
            checkout: vi.fn(),
        },
        shell: {
            exec: vi.fn().mockResolvedValue({ stdout: "", stderr: "", exitCode: 0 }),
            spawn: vi.fn(),
        },
        prompter: {
            confirm: vi.fn().mockResolvedValue(true),
            input: vi.fn(),
            select: vi.fn(),
        },
        logger: {
            info: vi.fn(),
            warn: vi.fn(),
            error: vi.fn(),
            verbose: vi.fn(),
            success: vi.fn(),
        },
    };
}

Test Fixtures

Test fixtures live in tests/fixtures/. Currently includes a minimal plugin at tests/fixtures/plugins/simplePlugin/index.ts.

Conventions

  • Test files mirror the module they test: core/resolver.tstests/unit/resolver.test.ts
  • Integration tests test execute*() functions, not commander
  • E2E tests use temp directories and XDG_CONFIG_HOME override
  • Mock only IOContext interfaces — no mocking internal functions