Skip to content

Commit

Permalink
tests - Full e2e test coverage
Browse files Browse the repository at this point in the history
  • Loading branch information
AlariCode committed Mar 31, 2020
1 parent e6b2514 commit afc9c5c
Show file tree
Hide file tree
Showing 13 changed files with 6,730 additions and 356 deletions.
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
/node_modules
/dist
/.idea
/.idea
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# Change log

## 1.5.1
- Fixed ack race condition
- Added tests

## 1.5.0
- Added error handler (thx to @mjarmoc)
- Added more debug info to error message (thx to @mjarmoc)
Expand Down
8 changes: 4 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ npm i nestjs-rmq
Setup your connection in root module:

```javascript
import { RMQModule } from 'nestjs-rmq';
import { RMQModule } from 'nestjs-tests';

@Module({
imports: [
Expand Down Expand Up @@ -102,7 +102,7 @@ export class MyIntercepter implements RMQIntercepterClass {
Config example with middleware and intercepters:

```javascript
import { RMQModule } from 'nestjs-rmq';
import { RMQModule } from 'nestjs-tests';

@Module({
imports: [
Expand Down Expand Up @@ -213,7 +213,7 @@ myMethod(numbers: number[]): number {
NestJS-rmq uses [class-validator](https://github.com/typestack/class-validator) to validate incoming data. To use it, decorate your route method with `Validate`:

```javascript
import { RMQController, RMQRoute, Validate } from 'nestjs-rmq';
import { RMQController, RMQRoute, Validate } from 'nestjs-tests';

@Validate()
@RMQRoute('my.rpc')
Expand Down Expand Up @@ -244,7 +244,7 @@ If your input data will be invalid, the library will send back an error without
To intercept any message to any route, you can use `@RMQPipe` decorator:

```javascript
import { RMQController, RMQRoute, RMQPipe } from 'nestjs-rmq';
import { RMQController, RMQRoute, RMQPipe } from 'nestjs-tests';

@RMQPipe(MyPipeClass)
@RMQRoute('my.rpc')
Expand Down
22 changes: 22 additions & 0 deletions e2e/contracts/mock.contracts.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { IsNumber, IsString } from 'class-validator';

export namespace SumContracts {
export const topic: string = 'sum.rpc';
export class Request {
@IsNumber({},{
each: true
})
arrayToSum: number[];
}
export class Response {
result: number;
}
}

export namespace NotificationContracts {
export const topic: string = 'notification.none';
export class Request {
@IsString()
message: string;
}
}
24 changes: 24 additions & 0 deletions e2e/controllers/api.controller.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { Controller, Get } from '@nestjs/common';
import { RMQService } from '../../lib';
import { NotificationContracts, SumContracts } from '../contracts/mock.contracts';

@Controller()
export class ApiController {
constructor(private readonly rmq: RMQService) {}

async sumSuccess(arrayToSum: number[]): Promise<SumContracts.Response> {
return this.rmq.send<SumContracts.Request, SumContracts.Response>(SumContracts.topic, { arrayToSum });
}

async sumFailed(arrayToSum: string[]): Promise<SumContracts.Response> {
return this.rmq.send<any, SumContracts.Response>(SumContracts.topic, { arrayToSum });
}

async notificationSuccess(message: string): Promise<void> {
return this.rmq.notify<NotificationContracts.Request>(NotificationContracts.topic, { message });
}

async notificationFailed(message: number): Promise<void> {
return this.rmq.notify<any>(NotificationContracts.topic, { message });
}
}
31 changes: 31 additions & 0 deletions e2e/controllers/microservice.controller.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { Controller } from '@nestjs/common';
import { RMQController, RMQError, RMQRoute, Validate } from '../../lib';
import { NotificationContracts, SumContracts } from '../contracts/mock.contracts';
import { ERROR_TYPE } from '../../lib/constants';

@Controller()
@RMQController()
export class MicroserviceController {
@RMQRoute(SumContracts.topic)
@Validate()
sumRpc({ arrayToSum }: SumContracts.Request): SumContracts.Response {
const result = arrayToSum.reduce((prev, cur) => prev + cur);
if (result === 0) {
throw new Error('My error from method');
}
if (result < 0 && result >= -10) {
throw new RMQError('My RMQError from method', ERROR_TYPE.RMQ, 0, 'data');
}
if (result < -10) {
return;
}
return { result: arrayToSum.reduce((prev, cur) => prev + cur) };
}

@RMQRoute(NotificationContracts.topic)
@Validate()
notificationNone({ message }: NotificationContracts.Request): void {
console.log(message);
return;
}
}
8 changes: 8 additions & 0 deletions e2e/docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
version: '3.1'
services:
rmq:
image: rabbitmq:3-management
restart: always
ports:
- "15672:15672"
- "5672:5672"
15 changes: 15 additions & 0 deletions e2e/jest-e2e.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@

{
"moduleFileExtensions": [
"ts",
"tsx",
"js",
"json"
],
"transform": {
"^.+\\.tsx?$": "ts-jest"
},
"testRegex": "/tests/.*\\.(e2e-test|e2e-spec).(ts|tsx|js)$",
"collectCoverageFrom" : ["src/**/*.{js,jsx,tsx,ts}", "!**/node_modules/**", "!**/vendor/**"],
"coverageReporters": ["json", "lcov"]
}
133 changes: 133 additions & 0 deletions e2e/tests/rmq.e2e-spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
import { Test } from '@nestjs/testing';
import { RMQModule, RMQService } from '../../lib';
import { INestApplication } from '@nestjs/common';
import { ApiController } from '../controllers/api.controller';
import { MicroserviceController } from '../controllers/microservice.controller';
import { ERROR_UNDEFINED_FROM_RPC } from '../../lib/constants';

describe('TestController', () => {
let api: INestApplication;
let apiController: ApiController;
let microserviceController: MicroserviceController;
let rmqService: RMQService;
let spyNotificationNone;

beforeAll(async () => {
const apiModule = await Test.createTestingModule({
imports: [
RMQModule.forRoot({
exchangeName: 'test',
connections: [
{
login: 'guest',
password: 'guest',
host: 'localhost',
},
],
queueName: 'test',
prefetchCount: 10,
serviceName: 'test-service'
}),
],
controllers: [ApiController, MicroserviceController],
}).compile();
api = apiModule.createNestApplication();
await api.init();

apiController = apiModule.get<ApiController>(ApiController);
microserviceController = apiModule.get<MicroserviceController>(MicroserviceController);
rmqService = apiModule.get<RMQService>(RMQService);
spyNotificationNone = jest.spyOn(microserviceController, 'notificationNone');
console.warn = jest.fn();
console.log = jest.fn();
});

describe('rpc', () => {
it('successful send()', async () => {
const { result } = await apiController.sumSuccess([1, 2, 3]);
expect(result).toBe(6);
});
it('request validation failed', async () => {
try {
const { result } = await apiController.sumFailed(['1', '2', '3']);
expect(result).not.toBe(6);
} catch (error) {
expect(error.message).toBe('each value in arrayToSum must be a number conforming to the specified constraints');
expect(error.type).toBeUndefined();
expect(error.code).toBeUndefined();
expect(error.data).toBeUndefined();
expect(error.service).toBe('test-service');
expect(error.host).not.toBeNull();
}
});
it('get common Error from method', async () => {
try {
const { result } = await apiController.sumSuccess([0,0,0]);
expect(result).not.toBe(0);
} catch (error) {
expect(error.message).toBe('My error from method');
expect(error.type).toBeUndefined();
expect(error.code).toBeUndefined();
expect(error.data).toBeUndefined();
expect(error.service).toBe('test-service');
expect(error.host).not.toBeNull();
}
});
it('get RMQError from method', async () => {
try {
const { result } = await apiController.sumSuccess([-1,0,0]);
expect(result).not.toBe(-1);
} catch (error) {
expect(error.message).toBe('My RMQError from method');
expect(error.type).toBe('RMQ');
expect(error.code).toBe(0);
expect(error.data).toBe('data');
expect(error.service).toBe('test-service');
expect(error.host).not.toBeNull();
}
});
it('get undefined return Error', async () => {
try {
const { result } = await apiController.sumSuccess([-11,0,0]);
expect(result).not.toBe(-11);
} catch (error) {
expect(error.message).toBe(ERROR_UNDEFINED_FROM_RPC);
expect(error.type).toBeUndefined();
expect(error.code).toBeUndefined();
expect(error.data).toBeUndefined();
expect(error.service).toBe('test-service');
expect(error.host).not.toBeNull();
}
});
});

describe('none', () => {
it('successful notify()', async () => {
const res = await apiController.notificationSuccess('test');
expect(spyNotificationNone).toBeCalledTimes(1);
expect(console.log).toBeCalledTimes(1);
expect(console.log).toHaveBeenCalledWith('test');
expect(res).toBeUndefined();
});
it('notify validation failed', async () => {
const res = await apiController.notificationFailed(0);
expect(spyNotificationNone).toBeCalledTimes(2);
expect(console.log).toBeCalledTimes(1);
expect(res).toBeUndefined();
});
});

afterAll(async () => {
await delay(500);
await rmqService.disconnect();
await api.close();
});
});

function delay(time: number) {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve();
}, time);
});
}
2 changes: 1 addition & 1 deletion lib/rmq.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,7 @@ export class RMQService {
}

private async reply(res: any, msg: Message, error: Error | RMQError = null) {
this.channel.ack(msg);
this.logger.recieved(`[${msg.fields.routingKey}] ${msg.content}`);
res = await this.intercept(res, msg, error);
await this.channel.sendToQueue(msg.properties.replyTo, Buffer.from(JSON.stringify(res)), {
Expand All @@ -181,7 +182,6 @@ export class RMQService {
...this.buildError(error),
},
});
this.channel.ack(msg);
this.logger.sent(`[${msg.fields.routingKey}] ${JSON.stringify(res)}`);
}

Expand Down
Loading

0 comments on commit afc9c5c

Please sign in to comment.