# Testing (/integrations/nestjs/how-to/testing)



Unit tests should not depend on a running KubeMQ broker. The `@kubemq/nestjs-transport/testing` entry point provides in-memory mocks that let you exercise the services that send messages and the handlers that process them entirely in isolation — no network, no broker, no flakiness. The subpath exports two classes:

| Export             | Replaces            | Use for                                              |
| ------------------ | ------------------- | ---------------------------------------------------- |
| `MockKubeMQClient` | `KubeMQClientProxy` | Services that call `client.send()` / `client.emit()` |
| `MockKubeMQServer` | `KubeMQServer`      | Handler logic invoked through `dispatch*` methods    |

`MockKubeMQClient` extends the NestJS `ClientProxy`, so it is a drop-in replacement anywhere a client proxy is injected. `MockKubeMQServer` records the handlers you register and drives them through the same Observable execution path the real transport uses, including the auto-ack and timeout behavior.

<Callout type="info">
  These mocks are designed for fast, broker-free unit tests. When you need true end-to-end verification against a real broker, see [Integration Testing](#integration-testing) below.
</Callout>

## Prerequisites [#prerequisites]

* `@kubemq/nestjs-transport` installed, with services and handlers already built against `KubeMQModule` / `KubeMQServer` (see [Getting Started with NestJS](/integrations/nestjs/tutorials/getting-started))
* No broker needed for the mock-based unit tests below; a running broker is only required for [Integration Testing](#integration-testing)

## Testing Services with MockKubeMQClient [#testing-services-with-mockkubemqclient]

`MockKubeMQClient` records every outbound call and returns the responses you stub. The workflow is:

1. `setResponse('pattern', value)` to stub the value a `send()` for that pattern resolves to (or `setError('pattern', err)` to make it reject).
2. Run the code under test.
3. Assert on the recorded `sendCalls` / `emitCalls` arrays — each entry is `{ pattern, data }`.
4. `reset()` between tests to clear recorded calls and stubbed responses.

Take a service that sends a command and emits an event:

```typescript title="order.service.ts"
import { Injectable, Inject } from '@nestjs/common';
import { ClientProxy } from '@nestjs/microservices';
import { firstValueFrom } from 'rxjs';

@Injectable()
export class OrderService {
  constructor(@Inject('KUBEMQ_SERVICE') private readonly client: ClientProxy) {}

  async createOrder(product: string, quantity: number): Promise<{ orderId: string }> {
    await this.client.connect();
    return firstValueFrom(
      this.client.send<{ orderId: string }>('nestjs-testing.orders.create', { product, quantity }),
    );
  }

  async notifyOrderShipped(orderId: string): Promise<void> {
    await this.client.connect();
    await firstValueFrom(
      this.client.emit('nestjs-testing.orders.shipped', { orderId }),
    );
  }
}
```

Provide the mock under the same injection token the service expects and assert on what it recorded:

```typescript title="mock-client.spec.ts"
import { Test } from '@nestjs/testing';
import { MockKubeMQClient } from '@kubemq/nestjs-transport/testing';
import { OrderService } from './order.service.js';

describe('MockKubeMQClient', () => {
  let orderService: OrderService;
  let mockClient: MockKubeMQClient;

  beforeEach(async () => {
    mockClient = new MockKubeMQClient();

    const module = await Test.createTestingModule({
      providers: [
        OrderService,
        { provide: 'KUBEMQ_SERVICE', useValue: mockClient },
      ],
    }).compile();

    orderService = module.get(OrderService);
  });

  afterEach(() => {
    mockClient.reset();
  });

  it('should send a command and receive a mocked response', async () => {
    mockClient.setResponse('nestjs-testing.orders.create', { orderId: 'ORD-001' });

    const result = await orderService.createOrder('WIDGET-42', 3);

    expect(result).toEqual({ orderId: 'ORD-001' });
    expect(mockClient.sendCalls).toHaveLength(1);
    expect(mockClient.sendCalls[0]).toEqual({
      pattern: 'nestjs-testing.orders.create',
      data: { product: 'WIDGET-42', quantity: 3 },
    });
  });

  it('should emit an event and record the call', async () => {
    await orderService.notifyOrderShipped('ORD-001');

    expect(mockClient.emitCalls).toHaveLength(1);
    expect(mockClient.emitCalls[0]).toEqual({
      pattern: 'nestjs-testing.orders.shipped',
      data: { orderId: 'ORD-001' },
    });
  });

  it('should simulate an error response', async () => {
    mockClient.setError('nestjs-testing.orders.create', new Error('Broker unavailable'));

    await expect(orderService.createOrder('WIDGET-42', 1)).rejects.toThrow('Broker unavailable');
  });
});
```

<Callout type="info">
  `sendCalls` and `emitCalls` are tracked separately. `client.send()` (Commands and Queries) is recorded in `sendCalls`; `client.emit()` (Events, Events Store, and Queues) is recorded in `emitCalls`. Assert on the right array for the pattern you are testing.
</Callout>

## Wiring the Mock via Dependency Injection [#wiring-the-mock-via-dependency-injection]

There are two ways to put `MockKubeMQClient` into the DI container.

<Tabs items="['Manual provider', 'forTest() shortcut']">
  <Tab value="Manual provider">
    Override the token explicitly with `useValue`. This is the most direct approach when you already construct the mock in the test and want full control over its lifecycle:

    ```typescript title="manual-provider.spec.ts"
    import { Test } from '@nestjs/testing';
    import { MockKubeMQClient } from '@kubemq/nestjs-transport/testing';
    import { OrderService } from './order.service.js';

    const mockClient = new MockKubeMQClient();

    const module = await Test.createTestingModule({
      providers: [
        OrderService,
        { provide: 'KUBEMQ_SERVICE', useValue: mockClient },
      ],
    }).compile();
    ```
  </Tab>

  <Tab value="forTest() shortcut">
    `KubeMQModule.forTest()` registers a `MockKubeMQClient` (and a `MockKubeMQServer`) under the module system for you. Import it instead of `forRoot` / `register`, then resolve the mocks from the module:

    ```typescript title="for-test.spec.ts"
    import { Test } from '@nestjs/testing';
    import { KubeMQModule } from '@kubemq/nestjs-transport';
    import { MockKubeMQClient, MockKubeMQServer } from '@kubemq/nestjs-transport/testing';
    import { OrderService } from './order.service.js';

    const module = await Test.createTestingModule({
      imports: [KubeMQModule.forTest({ name: 'KUBEMQ_SERVICE', isGlobal: true })],
      providers: [OrderService],
    }).compile();

    const orderService = module.get(OrderService);
    const mockClient = module.get(MockKubeMQClient);
    const mockServer = module.get(MockKubeMQServer);
    ```

    The same mock instance is registered under the `name` token, under `MockKubeMQClient`, and under the internal SDK client token, so a service that injects `'KUBEMQ_SERVICE'` and a test that resolves `MockKubeMQClient` share one object.
  </Tab>
</Tabs>

### forTest options [#fortest-options]

```typescript
KubeMQModule.forTest({ name: 'KUBEMQ_SERVICE', isGlobal: true })
```

| Parameter  | Type               | Default            | Description                                         |
| ---------- | ------------------ | ------------------ | --------------------------------------------------- |
| `name`     | `string \| symbol` | `'KUBEMQ_SERVICE'` | Injection token the mock client is registered under |
| `isGlobal` | `boolean`          | `false`            | Register the test module as a global module         |

<Callout type="info">
  Pass the same `name` you used in your production `KubeMQModule.register({ name })` so the service under test resolves the mock without any code changes. Both arguments are optional — calling `KubeMQModule.forTest()` with no options registers the mock under the default `'KUBEMQ_SERVICE'`.
</Callout>

## Testing Handlers with MockKubeMQServer [#testing-handlers-with-mockkubemqserver]

`MockKubeMQServer` tests the handler side. Register a handler for a pattern with `addHandler('pattern', fn)`, then drive it with one of the dispatch methods:

| Method                                         | Drives                  | Returns                           |
| ---------------------------------------------- | ----------------------- | --------------------------------- |
| `dispatchCommand(pattern, data)`               | A command handler       | `{ executed, response?, error? }` |
| `dispatchQuery(pattern, data)`                 | A query handler         | `{ executed, response?, error? }` |
| `dispatchEvent(pattern, data)`                 | An event handler        | `void`                            |
| `dispatchEventStore(pattern, data, sequence?)` | An events store handler | `void`                            |
| `dispatchQueueMessage(pattern, data)`          | A queue handler         | `{ acked, reQueued? }`            |

Each dispatch builds a real context object, converts the handler result to an Observable, and resolves it the same way the live transport does. For request-reply, `executed` is `true` when the handler returned a value and `false` when no handler is registered or the handler threw — in which case `error` carries the message. For queues, the mock auto-acks when the handler completes without explicitly calling `ack()` / `nack()` / `reQueue()`.

```typescript title="order.handler.ts"
export function handleCreateOrder(data: { product: string; quantity: number }): {
  orderId: string;
  product: string;
  status: string;
} {
  return {
    orderId: `ORD-${Date.now()}`,
    product: data.product,
    status: 'created',
  };
}
```

```typescript title="mock-server.spec.ts"
import { MockKubeMQServer } from '@kubemq/nestjs-transport/testing';
import { handleCreateOrder } from './order.handler.js';

describe('MockKubeMQServer', () => {
  let server: MockKubeMQServer;

  beforeEach(() => {
    server = new MockKubeMQServer();
  });

  afterEach(() => {
    server.reset();
  });

  it('should dispatch a command and return handler response', async () => {
    server.addHandler('nestjs-testing.orders.create', (data: unknown) =>
      handleCreateOrder(data as { product: string; quantity: number }),
    );

    const result = await server.dispatchCommand('nestjs-testing.orders.create', {
      product: 'WIDGET-42',
      quantity: 3,
    });

    expect(result.executed).toBe(true);
    expect(result.response).toMatchObject({ product: 'WIDGET-42', status: 'created' });
  });

  it('should dispatch an event to a handler', async () => {
    const received: unknown[] = [];
    server.addHandler('nestjs-testing.orders.notify', (data: unknown) => {
      received.push(data);
    });

    await server.dispatchEvent('nestjs-testing.orders.notify', {
      orderId: 'ORD-001',
      message: 'Order shipped',
    });

    expect(received).toHaveLength(1);
  });

  it('should auto-ack a queue message when the handler completes', async () => {
    server.addHandler('nestjs-testing.orders.process', (data: unknown) => {
      // process the message
    });

    const result = await server.dispatchQueueMessage('nestjs-testing.orders.process', {
      id: '123',
    });

    expect(result.acked).toBe(true);
  });

  it('should report error when handler throws', async () => {
    server.addHandler('nestjs-testing.orders.fail', () => {
      throw new Error('Validation failed');
    });

    const result = await server.dispatchCommand('nestjs-testing.orders.fail', {});
    expect(result.executed).toBe(false);
    expect(result.error).toContain('Validation failed');
  });
});
```

<Callout type="info">
  You can also test decorated handlers — or `@nestjs/cqrs` handlers — through a full NestJS `TestingModule` with the real DI container and no mocks at all. Build the module with `Test.createTestingModule({ providers: [MyHandler] })`, resolve the handler, and call its method directly. The transport decorators only attach routing metadata, so the handler method itself is plain TypeScript you can invoke and assert on.
</Callout>

## Integration Testing [#integration-testing]

When a live broker is available, an integration test exercises the full round-trip — serialization, gRPC transport, broker, and handler execution — instead of mocks. The transport's integration-test example builds a real module and sends real messages over the wire. Start a broker first; the gRPC API listens on `50000` and the shared HTTP and dashboard endpoints on `9090`:

<RunKubeMQ ports="[50000, 9090]" />

Then register the real `KubeMQModule` (not `forTest`) and assert on responses that came back from the broker:

```typescript title="integration.spec.ts"
import 'reflect-metadata';
import { Test, TestingModule } from '@nestjs/testing';
import { KubeMQModule } from '@kubemq/nestjs-transport';
import { OrderService } from './order.service.js';

describe('OrderService (integration)', () => {
  let module: TestingModule;
  let orderService: OrderService;

  beforeAll(async () => {
    module = await Test.createTestingModule({
      imports: [
        KubeMQModule.register({
          name: 'KUBEMQ_SERVICE',
          address: process.env.KUBEMQ_ADDRESS ?? 'localhost:50000',
          clientId: 'integration-test',
        }),
      ],
      providers: [OrderService],
    }).compile();

    orderService = module.get(OrderService);
  });

  afterAll(async () => {
    await module.close();
  });

  it('round-trips a command through a live broker', async () => {
    const result = await orderService.createOrder('WIDGET-42', 5);
    expect(result).toHaveProperty('orderId');
  });
});
```

<Callout type="info">
  Keep mock-based unit tests and broker-backed integration tests in separate suites. Unit tests run anywhere with no setup; integration tests require a reachable broker and are best gated behind an environment check or a dedicated CI job.
</Callout>

## Troubleshooting [#troubleshooting]

<Accordions>
  <Accordion title="Cannot find module '@kubemq/nestjs-transport/testing'">
    The testing mocks live behind a subpath export. This error means the installed package version does not expose the `./testing` entry in its `exports` map. Check the `exports` field in `node_modules/@kubemq/nestjs-transport/package.json` for a `./testing` key, and upgrade to a version that ships it:

    ```bash
    npm install @kubemq/nestjs-transport@latest
    ```

    The same applies to the `@kubemq/nestjs-transport/cqrs` subpath — it requires both a package version that exports it and the optional `@nestjs/cqrs` peer dependency.
  </Accordion>
</Accordions>

## Related [#related]

* [Module Configuration](/integrations/nestjs/how-to/module-configuration) for `forRoot` / `register` and the full `forTest` reference
* [Usage](/integrations/nestjs/how-to/usage) for the handler decorators and the `KubeMQRecord` builder
* [API reference](/integrations/nestjs/reference/api) for the complete export surface, including the testing subpath
