Skip to content

[Startup MVP recipes #9] Nest.js TypeORM Postgres Unit Testing (service only)with Jest, pg-mem

In this tutorial we will cover basic strategy of unit testing a service in Nest.js + TypeORM setup. We don’t create an environment for test purpose, but instead, we use pg-mem adapter to run a local in-memory Postgres simulator instance. In this way the db is always fresh out-of-box and it’s also very fast to run through all the tests.

Setup

When we initialized the Nest.js project, it should already ship the project with some Jest infra installed. To recap some knowledge from previous articles:

To run local jest testing with our env vars and config, we will add following commands to package.json

// package.json

{
    "test:local": "NODE_ENV=local jest",
    "test:local:t": "NODE_ENV=local jest -t",
}

// jest config section in package.json

"jest": {
    "moduleFileExtensions": [
      "js",
      "json",
      "ts"
    ],
    "rootDir": "src",
    "moduleNameMapper": {
      "^src/(.*)$": "<rootDir>/$1"
    },
    "testRegex": ".*\\.spec\\.ts$",
    "transform": {
      "^.+\\.(t|j)s$": "ts-jest"
    },
    "collectCoverageFrom": [
      "**/*.(t|j)s"
    ],
    "coverageDirectory": "../coverage",
    "testEnvironment": "node",
    "testPathIgnorePatterns": [
      "/node_modules/",
    ]
}

Install pg-mem

npm install pg-mem --include=dev

Setup pg-mem connection (TypeORM 0.2)

// setupConnection.ts

import { DataType, newDb } from 'pg-mem';
import { Connection } from 'typeorm';
import { SnakeNamingStrategy } from 'typeorm-naming-strategies';
import { v4 } from 'uuid';

export const setupConnection = async (entities: any[]) => {
  const db = newDb({
    autoCreateForeignKeyIndices: true,
  });

  db.public.registerFunction({
    implementation: () => 'test',
    name: 'current_database',
  });

  db.registerExtension('uuid-ossp', (schema) => {
    schema.registerFunction({
      name: 'uuid_generate_v4',
      returns: DataType.uuid,
      implementation: v4,
      impure: true,
    });
  });

  const connection: Connection = await db.adapters.createTypeormConnection({
    type: 'postgres',
    entities,
    namingStrategy: new SnakeNamingStrategy(),
  });

  await connection.synchronize();

  return connection;
};

The example unit test code

The key idea is that we create the testing module with its dependencies (so it doesn’t include every module from app module). In the module we need to override the repository that we provide and inject to the crud service, replacing it with the repository we obtained from pg-mem connection.

To run the test, use npm run test:local:t 'SmsTemplatesService'

describe('SmsTemplatesService', () => {
  let service: SmsTemplatesService;
  let connection: Connection;
  let oneSmsTemplateId: number;
  const createSmsTemplateInput: CreateSmsTemplateInput = {
    name: 'Test SMS Template Name',
    content: 'Test Content',
    cohort: 'newsletter',
  };

  beforeAll(async () => {
    connection = await setupConnection([SmsTemplate]);
    const module = await Test.createTestingModule({
      imports: [
        TypeOrmModule.forRoot({
          type: 'postgres',
          entities: [SmsTemplate],
          synchronize: true,
          keepConnectionAlive: true,
          namingStrategy: new SnakeNamingStrategy(),
        }),
        TypeOrmModule.forFeature([SmsTemplate]),
      ],
      providers: [SmsTemplatesService],
    })
      .overrideProvider(Repository)
      .useValue(connection.getRepository(SmsTemplate))
      .compile();

    service = module.get<SmsTemplatesService>(SmsTemplatesService);
  });

  afterAll(async () => {
    connection.close();
  });

  it('should be defined', () => {
    expect(service).toBeDefined();
  });

  it('should create one', async () => {
    const result = await service.createOneBySellerId(
      766,
      createSmsTemplateInput,
    );
    oneSmsTemplateId = result.id;
    ObjectTyped.keys(createSmsTemplateInput).forEach((key) => {
      expect(result[key]).toEqual(createSmsTemplateInput[key]);
    });
    expect(typeof result.id).toEqual('number');
  });

  it('should findAllBySellerId', async () => {
    const results = await service.findAllBySellerId(766);
    ObjectTyped.keys(createSmsTemplateInput).forEach((key) => {
      expect(results[0][key]).toEqual(createSmsTemplateInput[key]);
    });
  });

  it('should findOneBySellerId', async () => {
    const result = await service.findOneBySellerId(766, oneSmsTemplateId);
    ObjectTyped.keys(createSmsTemplateInput).forEach((key) => {
      expect(result[key]).toEqual(createSmsTemplateInput[key]);
    });
  });

  it('should update one', async () => {
    const sellerId = 766;
    const updateSmsTemplateInput: UpdateSmsTemplateInput = {
      id: oneSmsTemplateId,
      name: 'Test SMS Template Name: Updated Name',
      content: 'Test Content',
      cohort: 'newsletter',
    };
    const result = await service.updateOneBySellerId(
      sellerId,
      updateSmsTemplateInput,
    );
    ObjectTyped.keys(updateSmsTemplateInput).forEach((key) => {
      expect(result[key]).toEqual(updateSmsTemplateInput[key]);
    });
  });

  it('should remove one', async () => {
    const result = await service.removeOneBySellerId(766, oneSmsTemplateId);
    ObjectTyped.keys(result.returning).forEach((key) => {
      expect(result[key]).toEqual(createSmsTemplateInput[key]);
    });
    expect(result.affectedRows).toEqual(1);
  });
});

2 thoughts on “[Startup MVP recipes #9] Nest.js TypeORM Postgres Unit Testing (service only)with Jest, pg-mem”

  1. Pingback: [Startup MVP recipes #10] Nest.js Unit Testing: Mocking service and the universal mock - James Zhang

  2. Pingback: [Startup MVP recipes #11] Nest.js Run Unit Tests with Github Actions - James Zhang

Leave a Reply

Your email address will not be published.