Typeorm: Question: how to unit test without hitting the DB

Created on 29 Nov 2017  Ā·  49Comments  Ā·  Source: typeorm/typeorm

I'm trying to write some unit tests of code that uses typeorm without hitting the DB. I'm using getRepository in the code to do stuff.

In my tests ideally I'd like to call getRepository(SomeEntity) and then use sinon to sinon.mock(repository).expects('find').withArgs(...).returns(stuff). To avoid hitting the DB I thought I'd just not call createConnection if I'm in the unit tests context.

However I ran into the problem that getRepository calls connection manager which looks if it has a connection initialized and if not throws. What would be the recommended way of handling this? Is it possible to create a "dummy" connection somehow which doesn't try to connect anywhere?

testing discussion

Most helpful comment

IMO, it would be most effective to create an example of how to mock the typeorm repository with testing frameworks. It will be also good to utilize typedi together.

All 49 comments

Thanks, I had seen that one. However I'm not using testdouble.

The current (working) setup i have is this:

import * as typeorm from "typeorm";

function createFakeRepository() {
    return {
        find: function() {},
        findOne: function() {}
        // others
    }
}

// then in a test
 const fakeRepository = createFakeRepository();
 sinon.stub(typeorm, 'getRepository').withArgs(Profile).returns(fakeRepository);

 const profileRepositoryMock = sinon.mock(fakeRepository);
 profileRepositoryMock.expects('find').withArgs({emailAddress: profile.emailAddress}).returns(promise.resolve([profile]));

It works but is not pretty at all.

I'm not using testdouble.

You easily could be, though. We use both testdouble and sinon, they're both good at different things. testdouble is very good for this particular problem. sinon, not so much.

can you guys elaborate more on this question? Testing strategies aren't defined by typeorm yet and I would like to make them and provide all necessary tools for this purpose. Would be great if each of you describe about what testing-helper tools they would like to have in typeorm and provide a real world examples how they can be used.

For unit tests, using the approach I mentioned in the other thread you don't need anything from typeorm, it just works. That's why I suggested it. With minimal code in your tests It works today and works great.

For integration tests you probably do actually want a real database of some sort. Either a separate database/schema in your existing local dev database or a temporary sqlite database. If typeorm doesn't already then supporting some form of purely in-memory database may be a good thing here as well.

Expanding on my last comment, testdouble does mocking really well. sinon struggles in lots of ways. In particular, testdoubles ability to use ES2015 Proxy objects as mocks is really awesome. but testdouble only does mocking, there's lots of other utilities in sinon that are really awesome. it's just the mocking tools that are kind of lack-lustre.

Yeah I actually would prefer an in memory db implementation for testing to increase the speed of my automated tests and to decrease the need for a db to actually need to run for the sake of interface testing. Obviously there comes a stage where it would be nice to test out the full db but for automated regression testing it slows you down by an order of magnitude. What would it take to actually add this functionality for a ':memory:' driver?

As I already mentioned we should start with a detailed proposal, suggestions and real-world scenarios

I'll just repeat my previous comments with a little extra justifications and leave it at that. TL;DR version: typeorm doesn't need anything new implemented, in my opinion.

There's two cases I see here: unit tests and wider tests (integration, system, whatever you want to call them). Unit tests are defined as testing individual units in isolation, most commonly single functions or classes in isolation.

For unit testing there are mocking frameworks that can mock out the database entirely negating any need to have any sort of typeorm anything. Between the two tickets open about this there are examples in both the testdouble library and Sinon. The Sinon example is more verbose but hardly difficult or particularly long. Specifically, you don't want an in-memory database because you don't want to rely on (or ideally, not even load) typeorm in your unit tests. You simply want something that looks like typeorm - a mock.

For your wider system testing you want the same database as your production system will use. For wider tests you're sacrificing speed for correctness. Lets take an example from a project I'm currently working on. We use Postgres in production. We use a lot of Postgres specific stuff, specifically we use the uuid column type everywhere and also make pretty heavy use of JSONB. Should we expect those features to be supported in a custom in-memory database? Even if you don't use any custom stuff, SQL is pretty loosely specified. MySQL uses backticks for quoting table names. T-SQL uses square brackets. Postgres uses normal quotes. How do you resolve issues about basic SQL syntax? And at the end of the day, even if you solved all those issues, many of the database engines already have this solved anyway - MySQL provides the MEMORY storage engine for instance. And if you are using something that hasn't then you can always create a RAM disk for the database's data files while testing if they aren't already performant enough for your needs.

IMO, it would be most effective to create an example of how to mock the typeorm repository with testing frameworks. It will be also good to utilize typedi together.

@CaptJakk That's what the sqljs driver is for.

I do agree with @mscharley for the most part but if I'm unit-testing a class using the DB I like to write assertions on the result rather than on the way the result was obtained (for instance you could easily mess up an assertion on FK or UNIQUE integrity where an actual DB provides it for free).
But then I guess it depends on context. And testdouble.js looks super nice to mock the typeorm API šŸ‘


Example unit test with jest

describe('The Executor', () => {

    let executor: Executor;
    let connection: Connection;

    beforeEach(async () => {
        connection = await createConnection({
            type: 'sqljs',
            entities: [
                LogEntry,
                User,
                Topic
            ],
            logging: false,
            dropSchema: true, // Isolate each test case
            synchronize: true
        });
        executor = new Executor(connection);
    });

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

    describe('applyOperation', () => {
        it('inserts entities', async () => {
            const user = new User();
            user.id = 'testId';
            user.name = 'Jean test';
            const operation = insertOperation(
                user,
                'user',
                23
            );

            await executor.applyOperation(operation);
            const result = await connection.getRepository(User).findOneOrFail('testId');
            expect(result).toEqual(user);
        });
    });
});

Same issue here - creating a custom repository involves creating a valid connection.
I had a look at the code but mocking Connection, EntityManager, etc, seems to be a lot of work.

Started using sqljs but it does not support all datatypes (e.g. timestamp, which is valid with Postgres). As it's a port of SQLite, it should support all datatypes. I created an issue regarding this specific point.

Mocking everything still sounds as the best solution to me, though, it's easier said than done.

Is it possible to have a "test mode" on Typeorm in which every operation happens inside a transaction that is never committed?

@ericaprieto totally agree - this is a way how it should be solved
@pleerock

Spring framework has a really good support for testing, especially running each test in own transaction.
Transaction is runned with read uncommited isolation, then nothing fails due to lack of data.

As with above after completed test the transaction is never commited - roll back makes the database state constistent across all test cases.

I know here is coming different nature of nodejs and java - especially thread local context, however this is great solution and would be great to somehow allow developers to write tests in this way even with run tests one by one instead of concurrent execution of them.

Above solution will allow to build e2e/integration tests in predictable and simple way with possiblities to prepare data per test case or change database state during test execution.

I have a project that hooks into travisCI on push, and there is no database in that environment to run tests against.

I have been fussing with mock libraries on and off for weeks to try and get a stable solution.

I had a 'eureka' moment last night and discovered there is an in-memory database option via sql.js. This allows travis to run e2e tests, and alleviates my current mock nightmare. win-win.

I'm concerned I'll run into the timestamp issue documented here:
https://github.com/typeorm/typeorm/issues/1543

I'll be trying it out myself tonight. If my anecdote can help, or if you want code snippets to use as documentation, I'd be happy to offer assistance improving docs related to testing.

Can anyone provide working snippets and docs for this? Thanks!

I created a StackOverflow question for this:
https://stackoverflow.com/questions/51482701/how-do-you-mock-typeorms-getmanager-using-testdouble

If anyone wants to take a stab for credit (or for anyone stumbling on this in the future).

would love to have some working examples, especially for data mapper pattern unit-testing!

For testing this part of code

const repository = getRepository(TopProject);
const topProjects = await repository
        .createQueryBuilder('tp')
        .innerJoinAndMapOne('tp.customer', Customer, 'c', 'tp."customerId" = c.id')
        .innerJoinAndMapMany('tp.resourceTopProjects', ResourceTopProject, 'rtp', 'tp.id = rtp."topProjectId"')
        .innerJoinAndMapOne('rtp.resource', Resource, 'r', 'rtp."resourceId" = r.id')
        .where('tp."isActive" = :active', {active: true})
        .getMany();

// here some operation with result

I use sinon

import * as typeorm from 'typeorm';

const fakeQueryResult = [];

const fakeSelectQueryBuilder = sinon.createStubInstance(typeorm.SelectQueryBuilder);
fakeSelectQueryBuilder.innerJoinAndMapMany.returnsThis();
fakeSelectQueryBuilder.innerJoinAndMapOne.returnsThis();
fakeSelectQueryBuilder.where.returnsThis();
fakeSelectQueryBuilder.getMany.resolves(fakeQueryResult);

const fakeRepository = sinon.createStubInstance(typeorm.Repository);
fakeRepository.createQueryBuilder.returns(fakeSelectQueryBuilder);

const stubGetRepository = sinon.stub(typeorm, 'getRepository').returns(fakeRepository);

@pleerock
It would be nice to have interfaces.
For mock instance

example:

export class Connection implements IConnection {
// ...
}

and other.
Use case: I have typeorm extension

export class Builder {
    constructor(private readonly connection: Connection, /* ... */) {}
}

in order to write a unit test, I need to pass a Connection instance
but if there were interfaces I could write the code more freely and specify the interface.

export class Builder {
    constructor(private readonly connection: IConnection, /* ... */) {}
}

and create mock for me specific logic.
If you like going, I could make these changes.

@RobinCK no, we don't need interfaces. Since TypeScript is using structural typing, connection: Connection isn't necessary to be type of class Connection, it can also be class YourMockedConnection which can implement Connection (yes, you can implement class, not only interface) and can NOT implement it - no matter, structural typing will work anyway. Its not stupid Java dude! šŸ˜†

After banging my head against this for a few days I ended up writing a small "utility" file that bypasses the DB connection via class overrides.

I haven't fully tested it but I've so far been able to create entities and run custom repository methods.

I plan on extending the utility to import raw JSON fixtures into memory.

I'm hoping that I dont run into too many issues with this, as it seems promising so far.
... Even better would be proper unit test utilities built into TypeORM!

Here are the files

dbUtility.ts

This file stubs out the createConnection method, and overrides core class methods to bypass database connection.

dbUtility.ts

import * as typeorm from 'typeorm';
import { AlreadyHasActiveConnectionError } from 'typeorm/error/AlreadyHasActiveConnectionError';
import { Driver } from 'typeorm/driver/Driver';
import { DriverFactory } from 'typeorm/driver/DriverFactory';
import { PostgresDriver } from 'typeorm/driver/postgres/PostgresDriver';
import { stub } from 'sinon';

// Load all of my entities from the index
import * as entities from './../../src/entity'

// I'm using postgres, so I just force the PG driver, but dont connect
class myFakeDBDriver extends PostgresDriver {
  async connect(): Promise<void> {}
}

// again, just overwriting the driver factory method to connect to fake PG
class myDriverFactory extends DriverFactory {
  create(connection: Connection): Driver {
    return new myFakeDBDriver(connection); 
  }
}

// Overwriting the connection driver with an accessor to ensure we're using the fake driver.
// Its readonly property value wouldn't allow me to reset this from a child constructor.
class Connection extends typeorm.Connection {
  _driver: typeorm.Driver;
  get driver(): typeorm.Driver {
    return this._driver;
  }
  set driver(options) {
    this._driver = new myDriverFactory().create(this);
  }
}

export class ConnectionManager extends typeorm.ConnectionManager {

  // Complete copy from ConnectionManager.connect, but now the Connection class uses the fake driver.
  create(options: typeorm.ConnectionOptions): Connection {
    // check if such connection is already registered
    const existConnection = this.connections.find(connection => connection.name === (options.name || "default"));
    if (existConnection) {
      // if connection is registered and its not closed then throw an error
      if (existConnection.isConnected)
        throw new AlreadyHasActiveConnectionError(options.name || "default");
      // if its registered but closed then simply remove it from the manager
      this.connections.splice(this.connections.indexOf(existConnection), 1);
    }
    // create a new connection
    const connection = new Connection(options);
    this.connections.push(connection);
    return connection;
  }
}

// Stubbing out the createConnetion method to ensure that we use our class overrides.
const createConnection = stub(typeorm, 'createConnection').callsFake(async function (optionsOrName:typeorm.ConnectionOptions) {
  const connectionName = typeof optionsOrName === "string" ? optionsOrName : "default";
  const options = optionsOrName instanceof Object ? optionsOrName : await typeorm.getConnectionOptions(connectionName);
  return new ConnectionManager().create(options).connect();
});

export default createConnection({
  name: 'test',
  type: 'postgres',
  entities: Object.values(entities)
} as typeorm.ConnectionOptions);

Test File

This file imports dbUtility and then sets a custom repository.
Missing from the example is my test to create an entity from the repository, which worked.


generic.spec.ts

import 'source-map-support/register';
import * as chai from 'chai';
import * as sinonChai from 'sinon-chai';
import { ItemRepository } from './../src/repository'
import StubConnection from './dbUtility';

chai.use(sinonChai);

const { expect } = chai;

describe('Connection', async () => {

  let connection, Item;

  before(async () => {
    connection = await StubConnection;
    Item = connection.getCustomRepository(ItemRepository);
  })

  it('...', async () => {
    // Do some stuff with Item
  })

})

So this thread dates back to 2017. Has there been any consensus from within the project on best practices? I see a lot of different partially viable approaches.

@rightisleft not yet, we still collecting feedback :) Think we can already find a good thoughts in there :)

This is how I'm testing with jest and testdouble:

import { EntityManager } from "typeorm";
import { InjectManager } from "typeorm-typedi-extensions";
import Product from "./entity/Product";

class ProductResolver {
  constructor(
    @InjectManager() private readonly entityManager: EntityManager
  )

  listProducts(first: number): Promise<Product[]> {
    const productRepository = this.entityManager.getRepository(Product);
    return productRepository.find({ first });
  }
}
import td from "testdouble";
import { EntityManager, Repository } from "typeorm";
import Product from "../src/entity/Product";
import ProductResolver from "../src/resolvers/ProductResolver";

describe("ProductResolver", () => {
  test("listProducts", async () => {
    const productRepository = <Repository<Product>>{
      find: td.func('find')
    }
    const productEntities = [
      <Product>{ id: 1, name: "Product 1" }
    ];

    td.when(productRepository.find({ first: 10 })).thenResolve(productEntities);

    const entityManager = <EntityManager>{
      getRepository: td.func('getRepository')
    };
    td.when(entityManager.getRepository(Product)).thenReturn(productRepository);

    const resolver = new ProductResolver();
    const products = await resolver.listProducts(10);

    expect(products).toEqual(productEntities);
  })
});

A nice implementation of this Loopbacks's memory DB. We use it extensively for unit tests. Maybe it can serve as inspiration.

I did:

    TypeORM.createConnection({
      ...options,
      ...(isTest ? {
        database: ':memory:',
        type: 'sqlite',
      } : {
        ...mariaDB,
      }),
    })

also installed sqlite3 as a dev dep.

This does not solve the need for DB, but in-memory sqlite is a good choice as above.

Here is a standalone example with sqlite in-memory db and Jest:

import { createConnection, getConnection, Entity, getRepository } from "typeorm";
import { PrimaryGeneratedColumn, Column } from "typeorm";

@Entity()
export class MyEntity {
    @PrimaryGeneratedColumn()
    id?: number;

    @Column()
    name?: string;
}

beforeEach(() => {
    return createConnection({
        type: "sqlite",
        database: ":memory:",
        dropSchema: true,
        entities: [MyEntity],
        synchronize: true,
        logging: false
    });
});

afterEach(() => {
    let conn = getConnection();
    return conn.close();
});

test("store Joe and fetch it", async () => {
    await getRepository(MyEntity).insert({
        name: "Joe"
    });
    let joe = await getRepository(MyEntity).find({
        where: {
            id: 1
        }
    });
    expect(joe[0].name).toBe("Joe");
});

test("store Another and fetch it", async () => {
    await getRepository(MyEntity).insert({
        name: "Another"
    });
    let joe = await getRepository(MyEntity).find({
        where: {
            id: 1
        }
    });
    expect(joe[0].name).toBe("Another");
});

The trick is to close the connection afterEach, notice it gives Promise.

Sadly for developers that use PostgreSQL and jsonb fields sqlite for testing isn't really an option :/

I created a small repository, which shows how you can mock database for your blazing unit-tests :)
I tried to cover all TypeORM test cases

Here is an example

import * as typeorm from 'typeorm'
import { createSandbox, SinonSandbox, createStubInstance } from 'sinon'
import { deepEqual } from 'assert'

class Mock {
  sandbox: SinonSandbox

  constructor(method: string | any, fakeData: any, args?: any) {
    this.sandbox = createSandbox()

    if (args) {
      this.sandbox.stub(typeorm, method).withArgs(args).returns(fakeData)
    } else {
      this.sandbox.stub(typeorm, method).returns(fakeData)
    }
  }

  close() {
    this.sandbox.restore()
  }
}

describe('mocha => typeorm => getManager', () => {
  let mock: Mock

  it('getAll method passed', async () => {
    const fakeManager = createStubInstance(typeorm.EntityManager)
    fakeManager.find.resolves([post])

    mock = new Mock('getManager', fakeManager)

    const result = await postService.getAll()
    deepEqual(result, [post])
  })

  afterEach(() => mock.close())
})

P.S. Want to say thanks TypeORM contributors for amazing ORM library and @brdk for his comment

@YegorZaremba Hi, I followed your "small repository" code but I have doubt that where your Database connection/mocking database connection performing in unit test?
While I'm using same concept in my project it's showing connection default not found error. please can you help me in this.
Thank you.

@YegorZaremba Hi, I followed your "small repository" code but I have doubt that where your Database connection/mocking database connection performing in unit test?
While I'm using same concept in my project it's showing connection default not found error. please can you help me in this.
Thank you.

I'm having the issue. My setup includes TypeORM, TypeDI and routing-controllers.

image

Besides the fact I'm mocking the connection, for some reason it seems to be expecting it - I was wondering if it could be TypeDI not injecting the mocked connection.

import { createStubInstance } from 'sinon'
import { EntityManager } from 'typeorm'
import { deepEqual } from 'assert'

import { ContactService } from '../../../services/ContactService';
import Container from 'typedi';
import { ContactDTO } from '../../../services/dtos/contacts/ContactDTO';
import { Mock } from '../utils';

describe('mocha => typeorm => getManager', () => {
  let mock: Mock

  let newContact = new ContactDTO();
  newContact.name = 'DBA';
  newContact.is_active = true;
  newContact.employee_id = 87865;

  it('getAll method passed', async () => {
    const fakeManager = createStubInstance(EntityManager);
    fakeManager.find.resolves([newContact]);

    mock = new Mock('getManager', fakeManager);

    const result = await Container.get(ContactService).ListContacts();

    deepEqual(result, [newContact]);
  })

  afterEach(() => mock.close())
})

I saw @19majkel94 on this thread but I'm still a bit lost if I try to correlate what @YegorZaremba suggested in his example repo: https://github.com/YegorZaremba/typeorm-mock-unit-testing-example

Hi @jotamorais please can you help me on mocking the typeorm repository.
Thank you.
getClasses() {
return getManager().getRepository(Class)
.createQueryBuilder("class")
.where("class.fieldName = 'grade'")
.andWhere("class.active = 1")
.orderBy("class.sortOrder", "ASC")
.getMany();
}

this is my Repository.
so, I want to mock this repository to pass my unit test case.

@Ciantic I just get errors like Connection "default" was not found. and SQLite package has not been found installed. Try to install it: npm install sqlite3 --save with this approach??

I've added a question on SO https://stackoverflow.com/questions/57159356

It's a shame there aren't more examples of testing - I've spent a few days on this and gotton nowhere.

Update - managed to get this working now using Jest and approach of Ciantic
See https://github.com/AdditionAddict/angular-electron-typeorm-starter-plus-jest

I feel like the maintainers should be able to close this issue thanks to this fabulous answer above that uses Sql.js. Since it demonstrates using synchronize: true it's very very easy to write an api test.

https://github.com/typeorm/typeorm/issues/1267#issuecomment-483775861

I feel like the maintainers should be able to close this issue thanks to this fabulous answer above that uses Sql.js. Since it demonstrates using synchronize: true it's very very easy to write an api test.

#1267 (comment)

Unless I've missed something the proposed solution of using sql.js doesn't solve schema issues introduced when the database type is something like Postgres, where JSONB types are allowed and used.

I am using mongodb, routing-controllers, alsatian and TypeDI.

The current problem I am having is;

connection "default" was not found.

index.ts

import "reflect-metadata";
import * as cors from "cors";
import * as helmet from "helmet";
import * as bodyParser from "body-parser";
import { useExpressServer } from "routing-controllers";
import express = require("express");
import { createConnection, useContainer } from "typeorm";
import { NOT_FOUND, NO_CONTENT } from "http-status-codes";
import Container from "typedi";

(async () => {
    try {
        // Create a new express application instance
        const app = express();

        app.use(
            // Enable cross-origin Requests
            cors(),
            // Help us to secure our application by setting various HTTP headers
            helmet(),
            // Parses the client’s request from json into javascript objects
            bodyParser.json()
        );

        // Register typedi's Container with our controllers before connecting to database
        useContainer(Container);

        // Connects to the Database
        await createConnection();

        // Register express app instance with routing controllers
        useExpressServer(app, {
            controllers: [__dirname + "/controllers/*.ts"],
            routePrefix: "/api",
            cors: true,
            classTransformer: true,
            defaults: {
                // With this option, null will return 404 by default
                nullResultCode: NOT_FOUND,
                // With this option, void or Promise<void> will return 204 by default
                undefinedResultCode: NO_CONTENT
            }
        });

        // Listen to requests on 3001
        app.listen(3001, () => {
            // eslint-disable-next-line no-console
            console.log(`Server started on port 3001!`);
        });
    } catch (error) {
        // eslint-disable-next-line no-console
        console.error(error);
    }
})();

I have wrapped the TypeOrm getRepository<T>(entity) inside my own repo layer.

userRepo.ts

import { BaseRepo } from "./baseRepo";
import { User } from "../entities/User";
import {
    getRepository,
    Repository,
    FindConditions,
    QueryFailedError
} from "typeorm";
import { Service } from "typedi";
import { EmailAdressAlreadyInUseError } from "../errors/emailAddressAlreadyInUseError";
import { validate } from "class-validator";
import { InvalidDtoModelError } from "../errors/invalidDtoModelError";
import { EntityNotFoundError } from "typeorm/error/EntityNotFoundError";
import { Role } from "../models/Role";

@Service()
export class UserRepo implements BaseRepo<User> {
    private _userOrm: Repository<User>;

    public constructor() {
        this._userOrm = getRepository(User);
    }

    public async createItem(newItem: User): Promise<User> {
        // Try to save. If fails, the email is already in use
        let createdUser: User;
        try {
            newItem.email = newItem.email.toLowerCase();
            createdUser = await this._userOrm.save<User>(newItem);
        } catch (error) {
            if (error instanceof QueryFailedError) {
                return null;
            }
            // eslint-disable-next-line no-console
            console.error("error: ", error);
            throw error;
        }

        // If all ok, send CREATED response
        return createdUser;
    }

    /**
     * This function is intended for displaying content about a single user
     * to the front end, and hence is restricted to a set of accessible properties
     * @param id user guid
     */
    public async getItem(id: string): Promise<User> {
        // Get the user from database
        try {
            const user = await this._userOrm.findOneOrFail(id, {
                // We don't want to send the password on response
                select: User.accessibleProperties()
            });
            return user;
        } catch (error) {
            return null;
        }
    }

    /**
     * This function is intended for displaying content about a single user
     * to the front end, and hence is restricted to a set of accessible properties
     */
    public async getAllItems(): Promise<User[]> {
        //Get users from database
        const users = await this._userOrm.find({
            // We don't want to send the passwords on response
            select: User.accessibleProperties()
        });
        return users;
    }

    public async updateItem(updatedItem: User): Promise<User> {
        // Try to find the user in the database
        let user: User;
        try {
            user = await this._userOrm.findOneOrFail(updatedItem.id);
        } catch (error) {
            // eslint-disable-next-line no-console
            console.error(error);
            // If not found, send null
            return null;
        }

        Object.keys(updatedItem).map(p => {
            if (p !== undefined) {
                user[p] = updatedItem[p];
                switch (p) {
                    case "role":
                        user.role = updatedItem[p].toUpperCase() as Role;
                        break;
                    case "email":
                        user.email = updatedItem[p].toLowerCase();
                        break;
                }
            }
        });

        // Validate the new values on model
        const errors = await validate(user);
        if (errors.length > 0) {
            throw new InvalidDtoModelError(errors);
        }

        // If password is being updated then hash it
        if (Object.keys(updatedItem).find(p => p === "password")) {
            user.hashPassword();
        }

        // Try to save, if fails, that means email already in use
        try {
            await this._userOrm.save(user);
        } catch (error) {
            throw new EmailAdressAlreadyInUseError();
        }
    }

    public patchItem(id: string, patchedItem: User): Promise<User> {
        throw new Error("Method not implemented.");
    }

    public async deleteItem(id: string): Promise<boolean> {
        let user: User;
        try {
            user = await this._userOrm.findOneOrFail(id);
        } catch (error) {
            if (error instanceof EntityNotFoundError) {
                return false;
            }
            throw error;
        }

        this._userOrm.delete(user.id);
        return true;
    }

    /**
     * This function is intended for retreiving a complete user and does not hide
     * any sensative information such as hashed password
     * @param condition find condition
     */
    public async getUserBy(condition: FindConditions<User>): Promise<User> {
        let user: User;
        try {
            user = await this._userOrm.findOneOrFail(condition);
        } catch (error) {
            if (error instanceof EntityNotFoundError) {
                return null;
            }
            throw error;
        }
        return user;
    }
}

Here is my userRepo.test.ts

import { Expect, Test, Setup, SpyOnProperty } from "alsatian";
import { UserRepo } from "./userRepo";
import { User } from "../entities/User";
import { SaveOptions } from "typeorm";
import { UserMapper } from "../mappers/userMapper";

export class UserRepoTests {
    private sut: UserRepo;

    @Setup
    public setup() {
        const getRepository = {
            save: <User>(entity: User, options?: SaveOptions) =>
                Promise.resolve(() => entity)
        };

        SpyOnProperty(getRepository, "save");

        this.sut = new UserRepo();
    }

    @Test()
    public async createItemCallsSave() {
        const user: User = UserMapper.map({
            id: "1",
            studentId: "2",
            instructorId: "3",
            givenName: "Bob",
            familyName: "Ross",
            dateOfBirth: "0",
            email: "[email protected]",
            password: "password",
            role: "ADMIN"
        });
        const createItem = await this.sut.createItem(user);
        Expect(createItem.givenName).toBe(user.givenName);
    }
}

image

And here's the error I am getting. OP @adrianhara said that getRepository is calling the connection manager by its self. That is the issue here, so far I have seen a lot of people discussing how to structure their mocks. rather than discussing the issue of dealing with the connection manager in a way that works for all schema's.

If @dgreene1 thinks this issue can now be closed due to :memory:, then I would kindly ask how he expects people like myself (using mongodb) to deal with this issue.

import { Expect, Test, Setup, Teardown } from "alsatian";
import { User } from "../entities/User";
import { createConnection, getConnection, getRepository } from "typeorm";
import { UserMapper } from "../mappers/userMapper";

export class UserRepoTests {
    @Setup
    public async setup() {
        await createConnection({
            type: "mongodb",
            database: ":memory:",
            dropSchema: true,
            entities: [User],
            synchronize: true,
            logging: false,
            useNewUrlParser: true
        });
    }

    @Teardown
    public async teardown() {
        await getConnection().close();
    }

    @Test()
    public async createItemCallsSave() {
        await getRepository(User).insert(
            UserMapper.map({
                id: "1",
                studentId: "2",
                instructorId: "3",
                givenName: "Bob",
                familyName: "Ross",
                dateOfBirth: "0",
                email: "[email protected]",
                password: "password",
                role: "ADMIN"
            })
        );

        let user = await getRepository(User).findOneOrFail({
            where: {
                email: "[email protected]"
            }
        });
        Expect(user.givenName).toBe("[email protected]");
    }
}

image

:memory: is not a valid solution for mongodb.

Correct, the sqljs :memory: solution only works for sql. To accomplish this with mongo, one of the following would need to happen:

  • your tests would have to mock the mongo db (that would be very complicated)
  • your tests would need to spawn a real mongo database instance locally
  • your tests could be run in a docker container where a real mongo instance could be running (this has the advantage of automatic cleanup when the test stage of the docket build is done)
  • someone would have to invent an in memory mongo database

Those are the options I’m aware of. As for SQL users, sqljs works fine for anything that SQLite supports. For those things that Postgres does specifically, you’d need to mock the database for those tests (for instance if you’re testing something that uses PostGres’ JSONB type).

Side note: please don’t at tag me— I’m not a typeorm maintainer. So I don’t know a lot about how typeorm works. I’m just someone who likes writing unit tests and wanted to help a little bit. Hopefully the options above help you with testing. :)

@dgreene1 I tagged you as I was replying directly to you.

Thanks for taking the time to respond. I agree those options you describe are all very time consuming.

I found a very nice way of doing it. We cannot mock getRepository without it trying to get the default database instance. I will try mocking those parts it's calling later. But for now I decided to mock TypeOrm and it works.

What weird testing framework is this? It's called Alsatian

import { Expect, Test, SpyOn, Setup } from "alsatian";
import { User } from "../entities/User";
import { UserMapper } from "../mappers/userMapper";
import * as typeorm from "typeorm";

export class UserRepoTests {
    private sut: typeorm.Repository<User>;

    @Setup
    public setup() {
        SpyOn(typeorm, "getRepository").andReturn({
            save: <User>(entity: User) => entity
        });
    }

    @Test()
    public async createItemCallsSave() {
        // Maps from a UserDto to a User
        const user: User = UserMapper.map({
            id: "1",
            studentId: "2",
            instructorId: "3",
            givenName: "Bob",
            familyName: "Ross",
            dateOfBirth: "0",
            email: "[email protected]",
            password: "password",
            role: "ADMIN"
        });

        const userOrm = typeorm.getRepository(User);
        const createdUser = await userOrm.save(user);

        Expect(createdUser.role).toEqual("ADMIN");
    }
}

I know this is old, but for anyone looking for mongo memory server, check this out

I ran into this issue, did some work on the subject and wrote an article about it with some solutions for testing a TypeScript+Apollo GraphQL+TypeORM server using Docker+GitHub Actions, may be helpful for some users here: published on gitconnected

After banging my head against this for a few days I ended up writing a small "utility" file that bypasses the DB connection via class overrides.

I haven't fully tested it but I've so far been able to create entities and run custom repository methods.

I plan on extending the utility to import raw JSON fixtures into memory.

I'm hoping that I dont run into too many issues with this, as it seems promising so far.
... Even better would be proper unit test utilities built into TypeORM!

Here are the files

dbUtility.ts

This file stubs out the createConnection method, and overrides core class methods to bypass database connection.

dbUtility.ts

import * as typeorm from 'typeorm';
import { AlreadyHasActiveConnectionError } from 'typeorm/error/AlreadyHasActiveConnectionError';
import { Driver } from 'typeorm/driver/Driver';
import { DriverFactory } from 'typeorm/driver/DriverFactory';
import { PostgresDriver } from 'typeorm/driver/postgres/PostgresDriver';
import { stub } from 'sinon';

// Load all of my entities from the index
import * as entities from './../../src/entity'

// I'm using postgres, so I just force the PG driver, but dont connect
class myFakeDBDriver extends PostgresDriver {
  async connect(): Promise<void> {}
}

// again, just overwriting the driver factory method to connect to fake PG
class myDriverFactory extends DriverFactory {
  create(connection: Connection): Driver {
    return new myFakeDBDriver(connection); 
  }
}

// Overwriting the connection driver with an accessor to ensure we're using the fake driver.
// Its readonly property value wouldn't allow me to reset this from a child constructor.
class Connection extends typeorm.Connection {
  _driver: typeorm.Driver;
  get driver(): typeorm.Driver {
    return this._driver;
  }
  set driver(options) {
    this._driver = new myDriverFactory().create(this);
  }
}

export class ConnectionManager extends typeorm.ConnectionManager {

  // Complete copy from ConnectionManager.connect, but now the Connection class uses the fake driver.
  create(options: typeorm.ConnectionOptions): Connection {
    // check if such connection is already registered
    const existConnection = this.connections.find(connection => connection.name === (options.name || "default"));
    if (existConnection) {
      // if connection is registered and its not closed then throw an error
      if (existConnection.isConnected)
        throw new AlreadyHasActiveConnectionError(options.name || "default");
      // if its registered but closed then simply remove it from the manager
      this.connections.splice(this.connections.indexOf(existConnection), 1);
    }
    // create a new connection
    const connection = new Connection(options);
    this.connections.push(connection);
    return connection;
  }
}

// Stubbing out the createConnetion method to ensure that we use our class overrides.
const createConnection = stub(typeorm, 'createConnection').callsFake(async function (optionsOrName:typeorm.ConnectionOptions) {
  const connectionName = typeof optionsOrName === "string" ? optionsOrName : "default";
  const options = optionsOrName instanceof Object ? optionsOrName : await typeorm.getConnectionOptions(connectionName);
  return new ConnectionManager().create(options).connect();
});

export default createConnection({
  name: 'test',
  type: 'postgres',
  entities: Object.values(entities)
} as typeorm.ConnectionOptions);

Test File

This file imports dbUtility and then sets a custom repository.
Missing from the example is my test to create an entity from the repository, which worked.

generic.spec.ts

import 'source-map-support/register';
import * as chai from 'chai';
import * as sinonChai from 'sinon-chai';
import { ItemRepository } from './../src/repository'
import StubConnection from './dbUtility';

chai.use(sinonChai);

const { expect } = chai;

describe('Connection', async () => {

  let connection, Item;

  before(async () => {
    connection = await StubConnection;
    Item = connection.getCustomRepository(ItemRepository);
  })

  it('...', async () => {
    // Do some stuff with Item
  })

})

This is the best lead I have but when I tried it I'm getting ConnectionIsNotSetError: Connection with postgres database is not established. Check connection configuration.

How make fast test?

I was think about it and the easy way is going to be a memory database, but they slow quite a bit the test suite. So you can separete all the interactions with the database in classes and use memory database to test them. When you test another thing that calls those classes, you should mock them. So almost all of your test suites are going to test your own code.

It should be cool to have something in typeorm, that make mocking it easier. Because there are multiples ways to do the samething and anyone in their right mind would try to mock all the ways.

Sorry to bring up an old thread. One thing I do with knexjs is start a transaction globally before each test and then rollback at the end of the test. It looks something like this:

const { transaction, Model } = require('objection');

let knex;
let trx;

beforeEach(async () => {
  knex = Model.knex();
  trx = await transaction.start(knex);
  Model.knex(trx);
});

afterEach(async () => {
  await trx.rollback();
  Model.knex(knex);
});

afterAll(async () => {
  await knex.destroy();
});

Is it possible to do something like this with typeORM?

Reverting transaction strategy makes it integration test, not a unit/isolated test. They have their place, but these are expensive in terms of resources. It leads to smaller suites, tests not being run by developers and long deploys.
True custom repository(not CustomRepository) wrappers, seem to be the strongest approach to mocks. Ignore foreign key constraints and some of other problems related to doing Map<> based repo mocks.

The recommended approach in Java land is using actual databases:
https://www.testcontainers.org/

I'd personally never rely on unit tests on an ORM, even if it was possible; what if there's a bug in the ORM that only surfaces in your specific use cases? You can't catch those by mocking your database calls. Dry running is also scary and little different from using an actual database excluding the startup time (which should diminish as a factor as your tests grow anyway)

@HunderlineK That's what I suggest to do and we do those for some extra checks. But integration tests are heavy in many dimensions.

I'd personally never rely on unit tests on an ORM, even if it was possible; what if there's a bug in the ORM that only surfaces in your specific use cases?

You can apply this statement to any library you use, would that be Axiom, or int8l lib... whatever. There will always be a mixture of strictly "your" code, std-lib code and some of external libs code inside your unit tests.

You want to have these cases covered as much as it makes sense. Not too grained, run fast so developers can run them with confidence, and not expensive to maintain.

I was also stuck getting unit tests with mocked typeorm and Jest to work. My fallback option was to use a in-memory sqlite db, but as mentioned here, this is not an option for everyone.

In my case I want to test a Service that serves financial accounts stored in a database. The Service creates an account-repository with typeorm getRepository() function. I then use .find(), .save() etc. to interact with the repository and finally return the account (or undefined).

It might not be the best solution, but afer reading through a lot of comments and posts, this might help someone, who is new to typeorm like me, to get started. Also checkout the comments here from @lytc and @brdk .

//<app>/src/services/AccountService.ts

import { Account } from "./Account";
import { getRepository } from "typeorm";

export class AccountService {

  private accountRepo = getRepository(Account);

  public async getById(id : number): Promise<Account | undefined> {
    const account = await this.accountRepo.findOne(id)
    return account
  }

}

I have created a manual mock for typeorm, as follows. There might be a prettier way, but it works for me at the moment.

// <app>/__mocks__/typeorm.ts

export const PrimaryGeneratedColumn = jest.fn()
export const Column = jest.fn()
export const Entity = jest.fn()

export const getRepository = jest.fn().mockReturnValue({
    findOne: jest.fn() // return value will be set in the test
  })

My test are then defined like this:

//<app>/src/services/AccountService.test.ts
import { AccountService } from "./AccountService";
import { getRepository } from 'typeorm'
import {mocked} from 'ts-jest/utils'
import faker from 'faker'

jest.mock('typeorm')

describe('AccountService', () => {
  //https://github.com/kulshekhar/ts-jest/blob/master/docs/user/test-helpers.md
  const mockedGetRepo = mocked(getRepository(<jest.Mock>{}))

  beforeEach(() => {
    mockedGetRepo.findOne.mockClear()
  })

  test('getById', async () => {
    //Arrange
    const fakeAccount = {
      id: 123,
      iban: faker.finance.iban(),
      name: faker.finance.accountName()
    }
    mockedGetRepo.findOne.mockResolvedValue(fakeAccount) // return value
    //Act
    const accountService = new AccountService()
    const account = await accountService.getById(fakeAccount.id)
    //Assess
    expect(account).toEqual(fakeAccount)
  })

Hi @YegorZaremba ,
https://github.com/YegorZaremba/typeorm-mock-unit-testing-example/tree/master/test/unit/mocha
I followed the your sample code from the above link but i got the default connection error,can you please help me

Was this page helpful?
0 / 5 - 0 ratings