Detalhes do pacote

qtests

bijikyu215MIT2.0.0

Comprehensive Node.js testing framework with method stubbing, console mocking, environment management, and automatic stub resolution for axios, winston, and other modules

testing, stub, mock, winston

readme (leia-me)

qtests

A comprehensive Node.js testing framework with zero dependencies. Provides intelligent test generation, method stubbing, console mocking, and drop-in replacements for popular modules. Now with ES Module and TypeScript support!

🎉 Latest Updates (September 2025):

  • ✅ ESM + TypeScript Jest harness: runner always loads config/jest.config.mjs and passes --passWithNoTests for stable CI
  • ✅ HTTP testing shim alignment: TS shim re-exports a working JS shim with chainable .send() and proper req.body
  • ✅ Safe Mongoose mocking: Jest moduleNameMapper maps mongoose to qtests' manual mock (no real DB access)
  • ✅ Performance optimized: Jest-like batch execution with 69% speed improvement
  • ✅ Enhanced test generation: Smarter filtering, React-aware scaffolds, and safe defaults

🚀 Quick Start

npm install qtests --save-dev

qtests passively scaffolds its runner at your project root after install (via npm postinstall). No extra steps required.

Configure your project for ES modules by adding to package.json:

{
  "type": "module",
  "main": "index.ts"
}

TypeScript setup:

// Enable automatic stubbing
import './node_modules/qtests/setup.js';

// Your modules now use qtests stubs automatically
import axios from 'axios'; // Uses qtests stub
import winston from 'winston'; // Uses qtests stub

// Import with full type safety
import { stubMethod, mockConsole, testEnv, QtestsAPI } from 'qtests';

// Use with TypeScript intellisense
const restore = stubMethod(myObject, 'methodName', mockImplementation);

✨ Key Features

  • 🤖 Intelligent Test Generation - Automatically discovers and generates missing tests
  • 🎭 Method Stubbing - Temporarily replace object methods with automatic restoration
  • 📺 Console Mocking - Jest-compatible console spies with fallback for vanilla Node.js
  • 🌍 Environment Management - Safe backup and restore of environment variables
  • 📦 Module Stubs - Drop-in replacements for axios, winston, and other dependencies
  • 🔌 Offline Mode - Automatic stub resolution when external services are unavailable
  • 🏃 Lightweight Test Runner - Zero-dependency test execution engine
  • 🌐 HTTP Testing - Integration testing utilities (supertest alternative)
  • 📧 Email Mocking - Email testing without external mail services
  • 🆕 ES Module Support - Full compatibility with modern ES Module syntax
  • 🔷 TypeScript Support - Complete type definitions and intellisense
  • ⚡ Zero Dependencies - No production dependencies to bloat your project

🧩 Mock API (Runtime‑Safe)

qtests exposes a small, extensible mocking API that works at runtime without rewriting paths or adding heavy frameworks.

Defaults registered by setup:

  • axios → qtests stub (truthy, no network)
  • winston → qtests stub (no‑op logger with format/transports)
  • mongoose → project __mocks__/mongoose.js if present, or a minimal safe object

Usage:

import qtests from 'qtests';

// Register a custom module mock
qtests.mock.module('external-service', () => ({
  default: {
    call: async () => ({ ok: true })
  }
}));

// Now `require('external-service')` or `import ... from 'external-service'` returns the mock (CJS via require hook; ESM early via optional loader)

Notes:

  • Activation is runtime‑safe: a single require hook returns registered mocks; previously loaded CJS modules are best‑effort evicted from require.cache.
  • ESM projects can optionally use the loader for earliest interception:
    • node --loader=qtests/loader.mjs your-app.mjs
  • setup still runs first in Jest via config/jest-setup.ts so defaults are active before imports.

📖 Core Usage

Method Stubbing

import { stubMethod } from 'qtests';

const myObj = { greet: (name: string) => `Hello, ${name}!` };

// Stub the method
const restore = stubMethod(myObj, 'greet', () => 'Hi!');
console.log(myObj.greet('Brian')); // 'Hi!'

// Restore original
restore();
console.log(myObj.greet('Brian')); // 'Hello, Brian!'

Console Mocking

import { mockConsole } from 'qtests';

const spy = mockConsole('log');
console.log('test message');

console.log(spy.mock.calls); // [['test message']]
spy.mockRestore(); // Restore original console.log

Environment Management

import { testEnv } from 'qtests';

// Set test environment
testEnv.setTestEnv(); // Sets NODE_ENV=test, DEBUG=qtests:*

// Save and restore environment
const saved = testEnv.saveEnv();
process.env.TEST_VAR = 'modified';
testEnv.restoreEnv(saved); // TEST_VAR removed, original state restored

🧪 Unified Test Runner (API‑Only)

  • One command for everyone: npm test.
  • One runner: qtests-runner.mjs runs Jest via the programmatic API runCLI (no child processes, no tsx).
  • Honors: QTESTS_INBAND=1 (serial) and QTESTS_FILE_WORKERS=<n> (max workers).
  • Always uses project config and passWithNoTests, with cache=true and coverage=false.
  • Debugging: creates DEBUG_TESTS.md on failures; override with QTESTS_DEBUG_FILE=path or suppress with QTESTS_SUPPRESS_DEBUG=1.

Runner availability and generator behavior:

  • Postinstall scaffolding automatically creates qtests-runner.mjs at the project root (INIT_CWD) when missing.
  • npx qtests-generate ALWAYS (re)writes qtests-runner.mjs at the client root to keep the runner current.
  • Scaffolds config/jest.config.mjs (ignores dist/, build/) and config/jest-require-polyfill.cjs (ensures require(...) is available in ESM tests).
  • Scaffolds qtests-runner.mjs (API‑only runner).
  • Ensures helper scripts exist: scripts/clean-dist.mjs and scripts/ensure-runner.mjs.
  • Updates package.json scripts to:
    • pretest: node scripts/clean-dist.mjs && node scripts/ensure-runner.mjs
    • test: node qtests-runner.mjs

Stale runner protection:

  • scripts/ensure-runner.mjs silently replaces stale runners (e.g., spawn/parallel-mode or missing API‑only invariants) with the validated template.

Migration (from spawn‑based runners):

  • Run npx qtests-generate once to update the runner and scripts.
  • Ensure package.json contains the pretest and test commands above.
  • Remove any custom tsx/spawn‑based test commands.

CI verification:

  • npm run ci:verify validates runner policy, script wiring, dist hygiene, and Jest config.

🤖 Automatic Test Generation

CLI Usage (TypeScript ESM)

# Generate tests for entire project
npx qtests-generate

# Custom source directory
npx qtests-generate --src lib

# Custom source and test directories
npx qtests-generate --src app --test-dir tests/integration

# Only unit tests, preview without writing
npx qtests-generate --unit --dry-run

# Restrict to TypeScript files and skip existing tests
npx qtests-generate --include "**/*.ts" --exclude "**/*.test.ts"

# Use AST mode (requires typescript) and allow overwrites of generated tests
npx qtests-generate --mode ast --force

# Force React mode and add router wrapper
npx qtests-generate --react --with-router

# Backward-compatible alias
# (if your environment still references the old name)
npx qtests-ts-generate

Programmatic Usage (TypeScript ESM)

import { TestGenerator } from 'qtests';

const generator = new TestGenerator({
  SRC_DIR: 'src',
  TEST_DIR: 'tests/integration',
  include: ['**/*.ts'],
  exclude: ['**/*.test.ts'],
  mode: 'heuristic', // or 'ast' (requires `typescript`), falls back gracefully
});

await generator.generateTestFiles(false); // pass true for dry-run
const results = generator.getResults();
console.log(`Generated ${results.length} test files`);

Smart Discovery Features:

  • Walks entire project directory structure
  • Supports feature-first projects (tests alongside source files)
  • Detects existing tests to avoid duplicates
  • Handles multiple project structures (traditional, monorepo, mixed)

🔌 Module Stubs

Axios Stub

// Automatic when using qtests/setup
import axios from 'axios';

const response = await axios.get('/api');
// Returns: { data: {}, status: 200, statusText: 'OK', headers: {}, config: {} }

await axios.post('/api', data); // Enhanced response format

Winston Stub

// Automatic when using qtests/setup
import winston from 'winston';

const logger = winston.createLogger();
logger.info('This produces no output'); // Silent

Custom Module Stubs (Ad‑Hoc)

When you need to stub a niche dependency (beyond the built‑ins axios/winston) without changing qtests itself, register a custom stub in tests:

// Always load setup first so axios/winston are stubbed globally
import './node_modules/qtests/setup.js';

// Then register your ad‑hoc stub(s)
import { registerModuleStub } from 'qtests/utils/customStubs.js';

registerModuleStub('external-service-client', {
  ping: () => 'pong',
  get: async () => ({ ok: true })
});

// Now this resolves to your in‑memory stub even if the module is not installed
const client = require('external-service-client');
await client.get(); // { ok: true }

Notes:

  • Call registerModuleStub BEFORE the first require/import of that module.
  • Use unregisterModuleStub(id) and clearAllModuleStubs() for cleanup in afterEach.
  • Honors QTESTS_SILENT=1|true to reduce noise in CI logs.

🏃 Lightweight Test Runner

import { runTestSuite, createAssertions } from 'qtests';

const assert = createAssertions();

const tests = {
  'basic test': () => {
    assert.equals(1 + 1, 2);
    assert.isTrue(true);
  },
  'async test': async () => {
    const result = await Promise.resolve('done');
    assert.equals(result, 'done');
  }
};

runTestSuite('My Tests', tests);

🌐 HTTP Testing

// For generated API tests, a local shim is scaffolded at:
//   tests/generated-tests/utils/httpTest.ts (re-exports a JS shim)
//   tests/generated-tests/utils/httpTest.shim.js (implementation with .send())
// You can also import the same helpers directly from qtests if preferred.
import { httpTest } from 'qtests/lib/envUtils.js';

// Create mock Express app
const app = httpTest.createMockApp();
app.get('/users', (req, res) => {
  res.statusCode = 200;
  res.end(JSON.stringify({ users: [] }));
});

// Test the app — chainable .send() supported; JSON is defaulted and parsed
const response = await httpTest.supertest(app)
  .get('/users')
  .expect(200)
  .end();

📧 Email Testing

import { sendEmail } from 'qtests/lib/envUtils.js';

// Mock email sending
const result = await sendEmail.send({
  to: 'user@example.com',
  subject: 'Welcome',
  text: 'Welcome to our app!'
});

console.log(result.success); // true
console.log(sendEmail.getHistory()); // Array of sent emails

🛠️ Advanced Features

Offline Mode

import { offlineMode } from 'qtests';

// Enable offline mode
offlineMode.setOfflineMode(true);

// Get stubbed axios automatically
const axios = offlineMode.getAxios();
await axios.get('/api/data'); // Returns {} instead of real request

Integration with Jest

import { testHelpers } from 'qtests';

test('console output', async () => {
  await testHelpers.withMockConsole('log', (spy) => {
    console.log('test');
    expect(spy.mock.calls[0][0]).toBe('test');
  });
});

📚 API Reference

Core Methods

Method Description
stubMethod(obj, methodName, replacement) Replace object method with stub
mockConsole(method) Mock console methods with spy
testEnv.setTestEnv() Set standard test environment
testEnv.saveEnv() / restoreEnv() Backup/restore environment
offlineMode.setOfflineMode(enabled) Toggle offline mode

Test Generation

Method Description
new TestGenerator(options) Create test generator instance
generator.generateTestFiles(dryRun?) Generate missing tests (dryRun optional)
generator.getResults() Get list of generated files
CLI: npx qtests-generate Command-line test generation (alias: qtests-ts-generate)

Test Runner

Method Description
runTestSuite(name, tests) Execute test suite
createAssertions() Get assertion methods

🔷 TypeScript Configuration

To use qtests with ES modules and TypeScript, update your package.json:

{
  "type": "module",
  "main": "index.ts"
}

And ensure your tsconfig.json supports ES modules:

{
  "compilerOptions": {
    "target": "ES2020",
    "module": "ES2020",
    "moduleResolution": "node",
    "esModuleInterop": true,
    "allowSyntheticDefaultImports": true
  },
  "ts-node": {
    "esm": true
  }
}

Import Patterns

// Core utilities with full type safety
import { stubMethod, mockConsole, testEnv, TestGenerator, QtestsAPI } from 'qtests';

// Advanced utilities
import { httpTest, sendEmail, testSuite } from 'qtests/lib/envUtils.js';

// Module stubs
import { stubs } from 'qtests';
await stubs.axios.get('https://example.com');

🎯 Best Practices

🧰 CLI Reference

qtests-generate (alias: qtests-ts-generate)

  • Usage: qtests-generate [options]
  • Purpose: Scans source files and generates missing tests.
  • Options:
    • -s, --src <dir>: Source directory root to scan. Default: .
    • -t, --test-dir <dir>: Directory for integration/API tests. Default: tests/generated-tests
    • --mode <heuristic|ast>: Analysis mode. ast attempts TypeScript-based analysis if typescript is installed; falls back otherwise. Default: heuristic
    • --unit: Generate only unit tests
    • --integration: Generate only integration/API tests
    • --include <glob>: Include only matching files (repeatable)
    • --exclude <glob>: Exclude matching files (repeatable)
    • --dry-run: Preview actions; no files written or package.json updates
  • --force: Overwrite generated test files (filenames containing .GeneratedTest or legacy .GenerateTest)
    • --react: Force React mode (use jsdom, React templates)
    • --with-router: Wrap React tests with MemoryRouter when React Router is detected
    • --react-components: Opt-in to generating tests for React components
    • --no-react-components: Skip generating tests for React components (default)
    • -h, --help: Show help
    • -v, --version: Show version

Examples:

  • qtests-generate — scan current directory with defaults
  • qtests-generate --src lib — scan lib only
  • qtests-generate --unit --dry-run — preview unit tests only
  • qtests-generate --include "**/*.ts" --exclude "**/*.test.ts" — filter files
  • qtests-generate --mode ast --force — AST mode and overwrite generated tests

Notes:

  • On real runs (no --dry-run), the generator writes config/jest.config.mjs, config/jest-setup.ts, and creates qtests-runner.mjs.
  • The generated runner includes --config config/jest.config.mjs and --passWithNoTests.
  • The generated Jest config includes a moduleNameMapper for mongoose pointing to qtests' manual mock, preventing real DB access in unit tests.
  • Update of package.json test script is now opt-in via --update-pkg-script.
  • In --dry-run, none of the above files are written.
  • Enhanced file filtering automatically skips demo/, examples/, config/, and test utility directories.

React/Hook Templates and Providers

  • Components: By default, component test generation is disabled to reduce noise. Opt-in with --react-components. When enabled, components get a smoke render via React.createElement(Component, {}), asserting container exists only.
  • Hooks: Uses a probe component to mount the hook; avoids invalid direct calls.
  • Providers: If @tanstack/react-query is imported, renders inside QueryClientProvider. If react-hook-form is detected (or useFormContext/FormProvider is referenced), wraps with FormProvider using useForm().
  • Optional Router: With --with-router and when source imports react-router(-dom), wraps with MemoryRouter.
  • Required-props fallback: If a component appears to require props (TS inline types or propTypes.isRequired), generator falls back to a safe existence test instead of rendering.
  • Non-React modules: Emits safe existence checks or a module-load smoke test.
  • Skipped directories: __mocks__, __tests__, tests, test, generated-tests, manual-tests, node_modules, dist, build, .git.
  • API tests: Local tests/generated-tests/utils/httpTest.ts is scaffolded to re-export httpTest.shim.js, a minimal, dependency‑free HTTP test shim. Imports like ../utils/httpTest resolve without extra project config. The shim supports .send() and exposes req.body to handlers.

File Extension Strategy & JSX

  • Tests are emitted JSX-free using React.createElement, so unit/API tests default to .ts.
  • .tsx is only chosen when the generated test includes JSX (rare; currently templates avoid JSX).

Safety + Sanity Filters

  • Export filtering removes reserved/falsy/non-identifiers (e.g., default, function, undefined).
  • If no safe export remains, the generator emits a module smoke test instead of bogus per-export tests.
  • When valid React component/hook tests are emitted, the generator does not append generic “is defined” blocks.

qtests runner

  • Usage: qtests-ts-runner
  • Purpose: Discovers and runs tests in the project with a Jest-first strategy.
  • Behavior:
    • Discovers files matching .test|.spec|_test|_spec with .js|.ts|.jsx|.tsx
    • Tries npx jest with fast flags; falls back to verbose; finally runs with node if needed
    • Runs tests in parallel batches (2x CPU cores, capped by file count)
    • Performance Optimized: Jest-like batch execution achieving 69% speed improvement
  • Notes:
    • Automatically generated as qtests-runner.mjs by the test generator
    • Always passes --config config/jest.config.mjs and --passWithNoTests
    • Honors QTESTS_SUPPRESS_DEBUG=1|true to skip creating DEBUG_TESTS.md
    • Honors QTESTS_DEBUG_FILE to set a custom debug report path/name
    • Records Jest argv to runner-jest-args.json to aid debugging
    • Works with TypeScript ESM projects via ts-jest (scaffolded by the generator)

1. Always Load Setup First

// ✅ Correct
import './node_modules/qtests/setup.js';
import myModule from './myModule.js';

// ❌ Wrong  
import myModule from './myModule.js';
import './node_modules/qtests/setup.js';

2. Clean Up After Tests

test('example', () => {
  const restore = stubMethod(obj, 'method', stub);
  const spy = mockConsole('log');

  // ... test code ...

  // Always restore
  restore();
  spy.mockRestore();
});

3. Use Environment Helpers

import { testHelpers } from 'qtests';

test('environment test', async () => {
  await testHelpers.withSavedEnv(async () => {
    process.env.TEST_VAR = 'value';
    // Environment automatically restored
  });
});

🐛 Troubleshooting

Issue Solution
Stubs not working (CommonJS) Ensure require('qtests/setup') is called first
Stubs not working (ES Modules) Ensure import './node_modules/qtests/setup.js' is called first
TypeScript import errors Add "type": "module" to package.json and update tsconfig.json
ES Module syntax errors Ensure "module": "ES2020" in tsconfig.json
Console pollution Use mockConsole() to capture output
Environment leaks Use testHelpers.withSavedEnv() for isolation
Module not found Import advanced utilities from qtests/lib/envUtils
CLI not found Use npx qtests-generate (alias: qtests-ts-generate) or install globally
File extension errors Use .js extensions in ES module imports
Test generation creates tests for config files Enhanced filtering now automatically skips demo/, examples/, config/, and test directories
generateKey returns empty string Fixed in latest version - now correctly returns test keys like "test-api-key-user"
qtests-runner.mjs vs qtests-ts-runner.ts Use qtests-runner.mjs as the generated runner; the CLI command remains qtests-ts-runner

📄 License

MIT License - see LICENSE file for details.

🤝 Contributing

Contributions welcome! Please see our contributing guidelines and feel free to submit issues and pull requests.