DEV Community

Caio Lucas
Caio Lucas

Posted on

Tests with Jest and TypeORM

Hello Everyone!

So, today I'll show you how to configure your code to make tests with TypeORM and Jest.

Modules

First thing first, let's install some modules in our node environment. I'm using yarn:

yarn add jest ts-jest @types/jest -D

yarn add typeorm typescript pg

Then, let's create our tsconfig file:
yarn tsc --init

Jest configs

Ok, now we need to configure our jest.config.js and there are my conigs:

module.exports = {
  clearMocks: true,
  maxWorkers: 1,
  preset: 'ts-jest',
  testEnvironment: 'node',
  testMatch: [
    '**/__tests__/**/*.[jt]s?(x)',
    '!**/__tests__/coverage/**',
    '!**/__tests__/utils/**',
    '!**/__tests__/images/**',
  ],
};

I like to make a directory named tests in the root of the project to make tests.

TypeORM configs

And I like to create a ormconfig.js. Be comfortable to make a .json or .env.

module.exports = {
  name: 'default',
  type: 'postgres',
  host: 'localhost',
  port: 5432,
  username: 'postgres',
  password: 'postgres',
  database: 'tests',
  dropSchema: true,
  logging: false,
  synchroize: true,
  migrationsRun: true,

  entities: ['src/database/entities/*.ts'],
  migrations: ['src/database/migrations/*.ts'],
  cli: {
    entitiesDir: 'src/database/entities',
    migrationsDir: 'src/database/migrations',
  },
};

So, let the dropSchema: true because this will delete your data after the tests.

I like to let migrationsRun: true to automatically run migrations before the tests.

I'm using postgres, but be comfortable to use your favorite database.

Creating connection file

Let's create a connection.ts to export some functions to run in our tests.

import {createConnection, getConnection} from 'typeorm';

const connection = {
  async create(){
    await createConnection();
  },

  async close(){
    await getConnection().close(); 
  },

  async clear(){
    const connection = getConnection();
    const entities = connection.entityMetadatas;

    entities.forEach(async (entity) => {
      const repository = connection.getRepository(entity.name);
      await repository.query(`DELETE FROM ${entity.tableName}`);
    });
  },
};
export default connection;

The clear method will delete all data for every single entity registered in our connection.

Creating a test

So, in your tests, just put this code:

import connection from '../src/connection';

beforeAll(async ()=>{
  await connection.create();
});

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

beforeEach(async () => {
  await connection.clear();
});

it('creates a user', () => {
  // TODO
})

And that's it :)

Github project

If you want to see the full project just click here

Top comments (15)

Collapse
 
morcegon_22 profile image
Renan Andrade

Hello dude!
First of all, thank you by the great article, it help me a lot.
I've noticed that in the clear function you put an await function inside a forEach iterator, but in this case the promise will not work properly.
I solve this issue using the map instead the forEach and placing the map result inside a Promise.all(). This works great to me.

Collapse
 
sadra profile image
Sadra Isapanah Amlashi • Edited

Can you put your example here please?

Collapse
 
samrith profile image
Samrith Shankar

Here you go:

const entityDeletionPromises = entities.map((entity) => async() => {
  const repository = connection.getRepository(entity.name);
  await repository.query(`DELETE FROM ${entity.tableName}`);
});
await Promise.all(entityDeletionPromises);
Enter fullscreen mode Exit fullscreen mode
Thread Thread
 
jackbilestech profile image
jack-biles-technology • Edited

I'm currently using typeorm 0.2.31 and they allow for repo.clear() to truncate / drop the table

Collapse
 
kentbull profile image
Kent Bull

I ended up putting a separate call to testConn.create() in a module called my Jest Global setup hook so I could instruct Jest to refresh the database at the start of the test run by dropping the schema and recreating it from migrations by passing in dropSchema: true

Example:

async create(dropSchema: boolean) {
    return await createConnection({
      name: 'default',
      type: 'postgres',
      host: 'localhost',
      port: 5432,
      username: 'postgres',
      password: 'postgres',
      database: 'mydb_localtest',
      dropSchema: dropSchema,
      migrationsRun: dropSchema,
      entities: [__dirname + '/../entity/*.*'],
      migrations: [__dirname + '/../migration/*.*'],
    });
  },
Enter fullscreen mode Exit fullscreen mode

Then, if using TypeScript, add globalSetup and globalTeardown to your jest.config.js file like so (at bottom):

module.exports = {
  globals: {
    'ts-jest': {
      diagnostics: false,
      isolatedModules: true,
    },
  },
  preset: 'ts-jest',
  testEnvironment: 'node',
  testMatch: [
    "**/__tests__/**/*.spec.ts"
  ],
  setupFiles: ['./src/helpers/envSetup.ts'],
  roots: ['./__tests__/'],
  globalSetup: "./__tests__/globalSetup.js",
  globalTeardown: "./__tests__/globalTeardown.js"
};
Enter fullscreen mode Exit fullscreen mode

Then, in globalSetup.js you can call your TypeScript setup module by doing the following:

require("ts-node/register");
// using WillAvudim's solution: https://github.com/facebook/jest/issues/5164#issuecomment-376006851

const  { setup } = require( '../src/testing/MyTestLifecycle');
module.exports = async function globalSetup() {
  await setup();
  return null;
}
Enter fullscreen mode Exit fullscreen mode

The MyTestLifecycle module (in Typescript) just calls testConn.create(true) so that the schema is dropped at the start of all my tests and then re-created by running the migrations:

import { testConn } from './testConn';

export async function setup() {
  await testConn.create(true);
}
Enter fullscreen mode Exit fullscreen mode

Let me know when this is useful you!

Collapse
 
hantsy profile image
Hantsy Bai

I do not want to use a real database connection in the tests, how to test my custom Repository.
stackoverflow.com/questions/675802...

Collapse
 
hantsy profile image
Hantsy Bai • Edited
 await repository.query(`DELETE FROM ${entity.tableName}`);
Enter fullscreen mode Exit fullscreen mode

This will be a problem when you set the relations between tables.

Collapse
 
oxilor profile image
oxilor • Edited

You can do the following way:

afterEach(async () => {
  const entities = connection.entityMetadatas;
  for (const entity of entities) {
    const repository = connection.getRepository(entity.name);
    const schemaPrefix = entity.schema ? `${entity.schema}.` : '';
    await repository.query(
      `TRUNCATE ${schemaPrefix}${entity.tableName} RESTART IDENTITY CASCADE`
    );
  }
});
Enter fullscreen mode Exit fullscreen mode

The TypeORM also has the synchronize method, so you can simplify this code:

afterEach(async () => {
  await connection.synchronize(true);
});
Enter fullscreen mode Exit fullscreen mode

true means that TypeORM will drop all tables before syncing.

Collapse
 
kentbull profile image
Kent Bull

You can just drop the schema and recreate it from migrations. See my other response.

Collapse
 
alxrdev profile image
Alex Rodrigues Moreira

Valeu mano, vc me ajudou muito agora.

Collapse
 
nipunac95 profile image
NipunaC95

This is a wrong practice. unit tests should not use external resources like databases. In the other hand integration tests use api calls and check the whole functionality of the api. but whet you implemented here is somewhat in between that

Collapse
 
lucashang profile image
Lucas Hang • Edited

Man, that's beautiful.
Just a thing about the connection.clear inside beforeEach(), maybe it's better put it inside afterEach() and then another clear() inside afterAll().

Collapse
 
ezzabuzaid profile image
ezzabuzaid

Thank You!

You can use global setup and teardown files that can run the create and clear functions without repeating them in each file

jestjs.io/docs/en/configuration#gl...

Collapse
 
paulohsilvavieira profile image
Paulo Henrique

OMG, you saved my life!! Thx! Congrats by the awesome article

Collapse
 
hirokitakahashi profile image
Hiroki Takahashi

Thank you for this article.
I guess it's necessary to use "--runInBand" flag when running tests with this setting. Otherwise, DB connection may be closed before executing all test files