@tresdoce-nestjs-toolkit/test-utils
TypeScript icon, indicating that this package has built-in type declarations

2.0.1 • Public • Published
nestjs-logo

Tresdoce NestJS Toolkit
Test-Utils

Node.js Npm NestJS
GitHub license Release

Esta librería está pensada para ser utilizada en NestJS Starter o en este monorepo de funcionalidades, o cualquier proyecto que utilice una configuración centralizada, siguiendo la misma arquitectura del starter.

Al momento de realizar nuestros test, puede existir la necesidad de implementar una configuración para el ConfigModule , lo cual a veces se vuelve tedioso tener que estar creando un mock puntual para cada test, generando además duplicidad de código, como asi también la utilización de algún servicio que requiera nuestro código, como puede ser una Base de datos para test, y no tener la infraestructura disponible para dichas pruebas.

Por esta razón, y con el fin de desarrollar nuestros test de manera más ágiles y sin preocupaciones, surge la idea de esta librería que maneja de manera centralizada todo lo necesario para nuestros tests.

Glosario


📝 Requerimientos básicos

🛠️ Instalar dependencia

npm install -D @tresdoce-nestjs-toolkit/test-utils
yarn add -D @tresdoce-nestjs-toolkit/test-utils

👨‍💻 Uso

Base Configuration for test

//...
import { config } from '@tresdoce-nestjs-toolkit/test-utils';

describe('Suite for base config', () => {
  let app: INestApplication;

  beforeEach(async () => {
    const module: TestingModule = await Test.createTestingModule({
      imports: [
        ConfigModule.forRoot({
          isGlobal: true,
          load: [config],
        }),
        //...
      ],
    }).compile();

    app = module.createNestApplication();
    await app.init();
  });
  //...
});

Dynamic Configuration for test

//...
import { dynamicConfig } from '@tresdoce-nestjs-toolkit/test-utils';

describe('Suite for dynamic config', () => {
  let app: INestApplication;

  const args = {
    httOptions: {
      timeout: 5000,
      maxRedirects: 5,
    },
  };

  beforeEach(async () => {
    const module: TestingModule = await Test.createTestingModule({
      imports: [
        ConfigModule.forRoot({
          isGlobal: true,
          load: [dynamicConfig(args)],
        }),
        //...
      ],
    }).compile();

    app = module.createNestApplication();
    await app.init();
  });
  //...
});

😝 CreateMock

CreateMock es una función que facilita la creación de mocks para peticiones HTTP utilizando Nock como base.

import { createMock, cleanAllMock } from '@tresdoce-nestjs-toolkit/test-utils';

describe('MyController', () => {
  beforeEach(async () => {
    //...
    cleanAllMock();
  });
  //...
  it('should be return user data from mock', async () => {
    createMock({
      url: 'https://test.com/api/user/1',
      method: 'get',
      statusCode: 200,
      responseBody: {
        id: 1,
        firstName: 'John',
        lastName: 'Doe',
        email: 'john.doe@email.com',
      },
    });
    const user = await controller.getUser();
    //console.log(user); //Return mock response
    expect(user).toHaveProperty('firstName', 'John');
    expect(user).toHaveProperty('lastName', 'Doe');
    expect(user).toHaveProperty('email', 'john.doe@email.com');
  });
  //...
});

El responseBody del createMock admite también otros tipos de respuesta además de JSON.

Response body as JSON

createMock({
  url: 'http://example.com/api/data',
  method: 'get',
  statusCode: 200,
  responseBody: { success: true },
});

Response body as string

createMock({
  url: 'http://example.com/api/message',
  method: 'get',
  statusCode: 200,
  responseBody: 'Success message',
});

Response body as Buffer

createMock({
  url: 'http://example.com/api/file',
  method: 'get',
  statusCode: 200,
  responseBody: Buffer.from('Some binary data'),
});

Response body as function

Esta funcionalidad es ideal para tener fixtures de respuestas en archivos json y retornarlos.

createMock({
  url: 'http://example.com/api/dynamicData',
  method: 'get',
  statusCode: 200,
  responseBody: () =>
    JSON.parse(fs.readFileSync(path.resolve(__dirname, '../path/to/fixture.json'), 'utf8')),
});

En el caso de que tengas muchos fixtures y asi evitar duplicidad de código, pódes abstraer la función que retorna el JSON con el siguiente código.

const readFixtureFile = (filePath: string) => {
  const absolutePath = path.resolve(__dirname, filePath);
  const fileContents = fs.readFileSync(absolutePath, 'utf8');
  return JSON.parse(fileContents);
};

y luego pódes utilizarlo de la siguiente manera, ya que la respuesta es un JSON.

createMock({
  url: 'http://example.com/api/dynamicData',
  method: 'get',
  statusCode: 200,
  responseBody: readFixtureFile('../path/to/fixture.json'),
});

🧪 TestContainers

TestContainers es una librería que utiliza docker de por medio para poder instanciar un servicio durante el entorno de testing, tanto local como asi también en nuestros pipelines, y poder realizar las pruebas correctamente sin tener que estar consumiendo el servicio de algún entorno.

Esta librería viene con una configuración base para instanciar Redis, MongoDB, Postgres, MySql y Elasticsearch, pero también cuenta con la posibilidad de levantar cualquier otro servicio utilizando las imágenes de docker, por lo que se requiere tener Docker instalado.

Global Container

Instancia uno o más containers a partir de un archivo docker-compose.yml, está funcionalidad es ideal para instanciar los servicios de una aplicación y que disponible para consumir en todos los test.

Agregar globalSetup y globalTeardown a la configuración de jest de la aplicación (jest.config.ts)

//./jest.config.ts
import { jestConfig } from '@tresdoce-nestjs-toolkit/commons';
import * as dotenv from 'dotenv';

process.env.NODE_ENV = 'test';

dotenv.config({
  path: '.env.test',
});

module.exports = {
  ...jestConfig(),
  globalSetup: './jest.globalSetup.ts',
  globalTeardown: './jest.globalTeardown.ts',
};

Creamos los archivos jest.globalSetup.ts y jest.globalTeardown.ts en el root de la aplicación.

//./jest.globalSetup.ts
import { initDockerCompose } from '@tresdoce-nestjs-toolkit/test-utils';

const services = ['mongo', 'redis', 'elasticsearch'];
module.exports = initDockerCompose(services);
💬 Para ver en detalle todas las propiedades de la configuración, hace clic acá.

La función initDockerCompose recibe tres parámetros.

services: Es un array de string que sirve para especificar que servicios se quiere instanciar del docker-compose.yml, si no se envía el parámetro o se envía un array vacío, se inicializa todos los servicios definidos en el archivo.

  • Type: String[]
  • Required: false
  • Default: []
  • Example: ['mongo', 'redis', 'elasticsearch']

composeFilePath: Es el path de donde se encuentra el archivo docker-compose.yml.

  • Type: String
  • Required: false
  • Default: '.'

composeFile: Es el nombre del archivo de docker-compose.

  • Type: String
  • Required: false
  • Default: 'docker-compose.yml'
//./jest.globalSetup.ts
import { initDockerCompose } from '@tresdoce-nestjs-toolkit/test-utils';
import * as path from 'path';

const services = ['mongo', 'redis'];
const composeFilePath = path.resolve(__dirname, 'fixtures', 'docker-compose');
const composeFile = 'docker-compose-test.yml';

module.exports = initDockerCompose(services, composeFilePath, composeFile);
//./jest.globalTeardown.ts
import { closeDockerCompose } from '@tresdoce-nestjs-toolkit/test-utils';

module.exports = closeDockerCompose({ removeVolumes: false });
# ./docker-compose.yml

version: '3.9'

services:
  mongo:
    image: mongo:5.0
    container_name: local-mongo
    restart: always
    ports:
      - '27017:27017'
    environment:
      TZ: 'America/Argentina/Buenos_Aires'
      MONGO_INITDB_ROOT_USERNAME: root
      MONGO_INITDB_ROOT_PASSWORD: 123456
      MONGO_INITDB_DATABASE: test_db

  redis:
    image: redis:6.2-alpine
    container_name: local-redis
    restart: always
    ports:
      - '6379:6379'
    environment:
      TZ: 'America/Argentina/Buenos_Aires'
      REDIS_PORT: 6379
      REDIS_PASSWORD: 123456
      REDIS_HOST: cache
    command: ['redis-server', '--appendonly', 'yes', '--requirepass', '123456']

⚠️ En caso de fallas al correr en los pipelines, revisar que el docker-compose.yml este bien configurado y que el host del runner sea el mismo que usa docker. Ej.: http://localhost:6379 o http://docker:6379

Generic Container

Instancia un container con la imagen del servicio, está pensada para utilizarse para proyectos que utilizan un solo servicio.

//...
import { TCPostgresOptions, testContainers, delay } from '@tresdoce-nestjs-toolkit/test-utils';

jest.setTimeout(70000);
describe('TypeOrm - Postgres', () => {
  let app: INestApplication;
  let container: testContainers;

  // Instanciamos el test container
  beforeAll(async () => {
    // await delay(30000); // delay para inicializar el container
    container = await new testContainers('postgres:13', TCPostgresOptions);
    await container.start();
  });

  // Apagamos el container
  afterAll(async () => {
    await container.stop({ removeVolumes: true });
  });

  beforeEach(async () => {
    const module: TestingModule = await Test.createTestingModule({
      //...
    }).compile();

    app = module.createNestApplication();
    await app.init();
  });

  afterEach(async () => {
    await app.close();
  });

  //...
});

La clase testContainers requiere de dos parámetros, donde el primero es la imagen de docker junto a su tag, y el segundo son las configs para ese container.

Schema: new testContainers('<img-docker>:<tag-img-docker>', { config-container })

Para la configuración del contenedor, tiene disponibles las siguientes opciones.

{
  ports: [{
    container: number,
    host: number
  }],
  envs: {
    KEY: value
    //...
  },
  containerName: string,
  startupTimeout: number,
  reuse: boolean
}

La clase testContainers cuenta con algunas funciones que retorna información del contenedor.

  • getEnvs() retorna las variables de entorno enviadas al contenedor.
  • getContainer() retorna el contenedor instanciado.
  • getHost() retorna el host del contenedor instanciado, esto es util, ya que a veces el contenedor no se hostea en localhost
  • getName() retorna el nombre del contenedor.

Troubleshooting

Para solucionar el problema de failed: port is already allocated, es recomendable cambiar el puerto del host, manteniendo el del container con el default.

// Ejemplo para MongoDB
await new testContainers('mongo:5.0', {
  ...TCMongoOptions,
  ports: [
    {
      container: 27017,
      host: 27013,
    },
  ],
});

Limpiar los containers, images y volumes para probar en un entorno desde cero.

docker system prune --volumes
docker system prune -a
yarn test --force

📄 Changelog

Todos los cambios notables de este paquete se documentarán en el archivo Changelog.


Mex

Made with ❤

Readme

Keywords

Package Sidebar

Install

npm i @tresdoce-nestjs-toolkit/test-utils

Weekly Downloads

29

Version

2.0.1

License

MIT

Unpacked Size

83.4 kB

Total Files

40

Last publish

Collaborators

  • rudemex