Debugging & Testing Guide

Found an error? Have a suggestion?Edit this page on GitHub

Debugging & Testing Guide

This guide provides step-by-step instructions for debugging and testing the AsyncAPI CLI. Whether you're fixing a bug, adding a new feature, or understanding existing code, this document will help you navigate the debugging process effectively.

Table of Contents


Project Structure Overview

Understanding the codebase structure is essential for effective debugging:

1src/
2├── apps/
3   ├── api/           # REST API server (Express.js)
4      ├── controllers/   # API endpoint handlers
5      ├── middlewares/   # Request/response middleware
6      └── exceptions/    # API error types
7   └── cli/           # CLI application (oclif)
8       ├── commands/      # CLI command implementations
9       └── internal/      # Shared CLI utilities, flags, base classes
10├── domains/
11   ├── models/        # Domain models (SpecificationFile, Context, etc.)
12   └── services/      # Business logic services
13├── errors/            # Custom error classes
14├── utils/             # Utility functions
15└── interfaces/        # TypeScript interfaces
16
17test/
18├── fixtures/          # Test data files (AsyncAPI specs, etc.)
19├── helpers/           # Test utilities
20├── integration/       # Integration tests for CLI commands
21└── unit/              # Unit tests for services and controllers
22    ├── controllers/   # API controller tests
23    ├── services/      # Service layer tests
24    └── utils/         # Utility function tests

Setting Up Your Environment

1. Install Dependencies

npm install

2. Build the Project

npm run build

3. Set Up Environment Variables for Debugging

Create a .env file or export variables:

1# Enable development mode (verbose logging)
2export NODE_ENV=development
3
4# Disable analytics during testing
5export TEST=1
6
7# Set custom context file for testing
8export CUSTOM_CONTEXT_FILENAME="test.asyncapi-cli"
9export CUSTOM_CONTEXT_FILE_LOCATION=""

Debugging CLI Commands

Method 1: Using bin/run (Development Mode)

The bin/run script runs the CLI in development mode with TypeScript directly:

1# Run any command with debugging
2./bin/run validate ./path/to/asyncapi.yml
3
4# With verbose output
5DEBUG=* ./bin/run validate ./path/to/asyncapi.yml

Method 2: Using Node.js Inspector

1# Start with Node inspector
2node --inspect-brk ./bin/run validate ./path/to/asyncapi.yml
3
4# Then open Chrome DevTools at: chrome://inspect

Method 3: VS Code Debugging

Create .vscode/launch.json:

1{
2  "version": "0.2.0",
3  "configurations": [
4    {
5      "type": "node",
6      "request": "launch",
7      "name": "Debug CLI Command",
8      "program": "${workspaceFolder}/bin/run",
9      "args": ["validate", "./test/fixtures/specification.yml"],
10      "env": {
11        "NODE_ENV": "development",
12        "TEST": "1"
13      },
14      "sourceMaps": true,
15      "outFiles": ["${workspaceFolder}/lib/**/*.js"]
16    },
17    {
18      "type": "node",
19      "request": "launch",
20      "name": "Debug Current Test File",
21      "program": "${workspaceFolder}/node_modules/mocha/bin/_mocha",
22      "args": [
23        "--require", "ts-node/register",
24        "--require", "tsconfig-paths/register",
25        "--timeout", "100000",
26        "${file}"
27      ],
28      "env": {
29        "NODE_ENV": "development",
30        "TEST": "1",
31        "CUSTOM_CONTEXT_FILENAME": "test.asyncapi-cli"
32      },
33      "sourceMaps": true
34    }
35  ]
36}

Method 4: Adding Console Logs

For quick debugging, add logs in command files:

1// In src/apps/cli/commands/validate.ts
2async run() {
3  const { args, flags } = await this.parse(Validate);
4  
5  // Debug: Log parsed arguments
6  console.log('DEBUG - Args:', JSON.stringify(args, null, 2));
7  console.log('DEBUG - Flags:', JSON.stringify(flags, null, 2));
8  
9  // ... rest of the command
10}

Debugging the API Server

Starting the API in Development Mode

1# Start with hot-reload
2npm run api:dev
3
4# Or manually with debugging
5NODE_ENV=development DEBUG=* node ./lib/apps/api/server.js

Testing API Endpoints

1# Validate endpoint
2curl -X POST http://localhost:3000/v1/validate \
3  -H "Content-Type: application/json" \
4  -d '{"asyncapi": "asyncapi: 3.0.0\ninfo:\n  title: Test\n  version: 1.0.0\nchannels: {}"}'
5
6# Parse endpoint
7curl -X POST http://localhost:3000/v1/parse \
8  -H "Content-Type: application/json" \
9  -d '{"asyncapi": "..."}'

Debugging API Controllers

Add middleware logging:

1// In src/apps/api/middlewares/logger.middleware.ts
2// Logs are automatically enabled in development mode

Debugging Services

ValidationService

The ValidationService handles document validation. To debug:

1// Test the service directly
2import { ValidationService } from '@services/validation.service';
3import { load } from '@models/SpecificationFile';
4
5async function debugValidation() {
6  const service = new ValidationService();
7  const specFile = await load('./path/to/spec.yml');
8  
9  const result = await service.validateDocument(specFile, {
10    'fail-severity': 'error',
11    'log-diagnostics': true,
12  });
13  
14  console.log('Validation Result:', JSON.stringify(result, null, 2));
15}

ConversionService

1import { ConversionService } from '@services/convert.service';
2import { load } from '@models/SpecificationFile';
3
4async function debugConversion() {
5  const service = new ConversionService();
6  const specFile = await load('./path/to/spec.yml');
7  
8  const result = await service.convertDocument(specFile, {
9    format: 'asyncapi',
10    'target-version': '3.0.0',
11    perspective: 'server',
12  });
13  
14  console.log('Conversion Result:', JSON.stringify(result, null, 2));
15}

GeneratorService

1import { GeneratorService } from '@services/generator.service';
2
3async function debugGenerator() {
4  const service = new GeneratorService();
5  // Add your debugging logic
6}

Writing Tests

Test File Naming Convention

  • Unit tests: test/unit/<category>/<name>.test.ts
  • Integration tests: test/integration/<command>.test.ts

Unit Test Structure

1// test/unit/services/my-service.test.ts
2import { expect } from 'chai';
3import { MyService } from '../../../src/domains/services/my.service';
4
5describe('MyService', () => {
6  let service: MyService;
7
8  beforeEach(() => {
9    service = new MyService();
10  });
11
12  describe('methodName', () => {
13    it('should do something when given valid input', async () => {
14      // Arrange
15      const input = { /* test data */ };
16      
17      // Act
18      const result = await service.methodName(input);
19      
20      // Assert
21      expect(result.success).to.be.true;
22      expect(result.data).to.exist;
23    });
24
25    it('should handle errors gracefully', async () => {
26      // Arrange
27      const invalidInput = null;
28      
29      // Act
30      const result = await service.methodName(invalidInput);
31      
32      // Assert
33      expect(result.success).to.be.false;
34      expect(result.error).to.include('error message');
35    });
36  });
37});

Integration Test Structure (CLI Commands)

1// test/integration/mycommand.test.ts
2import { expect, test } from '@oclif/test';
3import path from 'path';
4
5const validSpec = path.resolve(__dirname, '../fixtures/specification.yml');
6const invalidSpec = path.resolve(__dirname, '../fixtures/specification-invalid.yml');
7
8describe('mycommand', () => {
9  describe('with valid input', () => {
10    test
11      .stdout()
12      .command(['mycommand', validSpec])
13      .it('should succeed with valid specification', (ctx) => {
14        expect(ctx.stdout).to.contain('Success');
15      });
16  });
17
18  describe('with invalid input', () => {
19    test
20      .stderr()
21      .command(['mycommand', invalidSpec])
22      .exit(1)
23      .it('should fail with invalid specification', (ctx) => {
24        expect(ctx.stderr).to.contain('Error');
25      });
26  });
27
28  describe('with flags', () => {
29    test
30      .stdout()
31      .command(['mycommand', validSpec, '--output', 'result.json'])
32      .it('should handle output flag', (ctx) => {
33        expect(ctx.stdout).to.contain('saved');
34      });
35  });
36});

API Controller Test Structure

1// test/unit/controllers/my.controller.test.ts
2import request from 'supertest';
3import { App } from '../../../src/apps/api/app';
4import { MyController } from '../../../src/apps/api/controllers/my.controller';
5
6describe('MyController', () => {
7  let app: App;
8
9  beforeEach(async () => {
10    app = new App([new MyController()]);
11    await app.init();
12  });
13
14  describe('[POST] /v1/myendpoint', () => {
15    it('should return 200 with valid input', async () => {
16      return request(app.getServer())
17        .post('/v1/myendpoint')
18        .send({ data: 'valid' })
19        .expect(200)
20        .then((response) => {
21          expect(response.body).to.have.property('result');
22        });
23    });
24
25    it('should return 422 with invalid input', async () => {
26      return request(app.getServer())
27        .post('/v1/myendpoint')
28        .send({})
29        .expect(422);
30    });
31  });
32});

Using Test Fixtures

1import path from 'path';
2import { load } from '@models/SpecificationFile';
3
4// Load test fixtures
5const fixturesPath = path.resolve(__dirname, '../../fixtures');
6
7async function loadTestSpec(filename: string) {
8  return load(path.join(fixturesPath, filename));
9}
10
11// Usage in tests
12describe('MyTest', () => {
13  it('should handle v3 spec', async () => {
14    const spec = await loadTestSpec('specification-v3.yml');
15    // ... test logic
16  });
17});

Running Tests

Run All Tests

npm test

Run Only CLI Tests

npm run cli:test

Run Only Unit Tests

npm run unit:test

Run a Single Test File

npm run test:one -- test/integration/validate.test.ts

Run Tests with Coverage

1npm run cli:test
2# Coverage report is generated in ./coverage/

Run Tests in Watch Mode (Development)

1# Using nodemon for file watching
2npm run dev
3
4# Then run tests manually when needed
5npm run unit:test

Common Issues & Solutions

Issue 1: "Cannot find module '@utils/proxy'"

Cause: TypeScript path aliases not resolved.

Solution:

1# Rebuild the project
2npm run build

Issue 2: Permission Denied Errors

Cause: Files created by root or different user.

Solution:

1# Fix permissions
2sudo chown -R $(whoami) ./lib ./node_modules ./.nyc_output

Issue 3: Test Context File Conflicts

Cause: Tests using the same context file as development.

Solution:

1# Set test-specific context file
2export CUSTOM_CONTEXT_FILENAME="test.asyncapi-cli"
3export CUSTOM_CONTEXT_FILE_LOCATION=""

Issue 4: "ECONNREFUSED" in API Tests

Cause: API server not started or wrong port.

Solution:

1# Ensure the app is initialized in tests
2const app = new App([new MyController()]);
3await app.init();  // Don't forget this!

Issue 5: Async Test Timeouts

Cause: Default timeout too short for async operations.

Solution:

1// Increase timeout for specific tests
2it('should handle slow operation', async function() {
3  this.timeout(30000);  // 30 seconds
4  // ... test logic
5});

Issue 6: ESLint Errors in Tests

Cause: Using testing patterns that trigger lint rules.

Solution:

1// Add eslint disable for specific patterns
2/* eslint-disable @typescript-eslint/no-unused-expressions */
3expect(result).to.be.true;  // Chai assertions

Debugging Tools & Tips

1. Enable Verbose Logging

DEBUG=* ./bin/run validate spec.yml

2. Use Node.js Inspector

1node --inspect-brk ./bin/run validate spec.yml
2# Open chrome://inspect in Chrome

3. Print Stack Traces

1try {
2  // risky operation
3} catch (error) {
4  console.error('Stack trace:', error.stack);
5  throw error;
6}

4. Use TypeScript Source Maps

Ensure tsconfig.json has:

1{
2  "compilerOptions": {
3    "sourceMap": true
4  }
5}

5. Debug Parser Output

1import { Parser } from '@asyncapi/parser';
2
3const parser = new Parser();
4const { document, diagnostics } = await parser.parse(specContent);
5
6console.log('Parsed document:', JSON.stringify(document?.json(), null, 2));
7console.log('Diagnostics:', JSON.stringify(diagnostics, null, 2));

6. Inspect Service Results

All services return a ServiceResult type:

1interface ServiceResult<T> {
2  success: boolean;
3  data?: T;
4  error?: string;
5}
6
7// Always check both success and data
8if (result.success && result.data) {
9  console.log('Success:', result.data);
10} else {
11  console.log('Error:', result.error);
12}

7. Test Commands Interactively

1# Build and run immediately
2npm run build && ./bin/run validate ./test/fixtures/specification.yml

Quick Reference

TaskCommand
Build projectnpm run build
Run all testsnpm test
Run CLI testsnpm run cli:test
Run unit testsnpm run unit:test
Run single testnpm run test:one -- <path>
Lint codenpm run lint
Fix lint issuesnpm run lint:fix
Start API dev servernpm run api:dev
Debug CLI command./bin/run <command> <args>
Debug with inspectornode --inspect-brk ./bin/run <command>

Getting Help

If you're still stuck:

  1. Check existing tests for similar functionality
  2. Look at the error messages and stack traces
  3. Search for similar issues in the GitHub Issues
  4. Ask in the AsyncAPI Slack #tooling channel
Was this helpful?
Help us improve the docs by adding your contribution.
OR
Github:AsyncAPICreate Issue on GitHub