Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to mock typeorm connection

In integration tests I am using the following snippets to create connection

import {Connection, createConnection} from 'typeorm';
// @ts-ignore
import options from './../../../ormconfig.js';

export function connectDb() {
  let con: Connection;

  beforeAll(async () => {
    con = await createConnection(options);
  });

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

}

I am trying to unit test a class which calls typeorm repository in one of its method and without call that helper function connectDb() above I get the following error which is expected of course.

ConnectionNotFoundError: Connection "default" was not found.

My question is how can I mock connection. I have tried the following without any success

import typeorm, {createConnection} from 'typeorm';
// @ts-ignore
import options from "./../../../ormconfig.js";

const mockedTypeorm = typeorm as jest.Mocked<typeof typeorm>;

jest.mock('typeorm');

 beforeEach(() => {
    //mockedTypeorm.createConnection.mockImplementation(() => createConnection(options)); //Failed
    mockedTypeorm.createConnection = jest.fn().mockImplementation(() => typeorm.Connection);

    MethodRepository.prototype.changeMethod = jest.fn().mockImplementation(() => {
      return true;
    });
  });

Running tests with that kind of mocking gives this error

TypeError: decorator is not a function

Note: if I call connectDb() in tests everything works fine. But I don't want to do that since it takes too much time as some data are inserted into db before running any test. Some codes have been omitted for simplicity. Any help will be appreciated

like image 677
Nux Avatar asked Mar 23 '20 12:03

Nux


1 Answers

After a bunch of research and experiment I've ended up with this solution. I hope it works for someone else who experienced the same issue...

  • it does not need any DB connection
  • testing service layer content, not the DB layer itself
  • test can cover all the case I need to test without hassle, I just need to provide the right output to related typeorm methods.

This is the method I want to test

@Injectable()
export class TemplatesService {
  constructor(private readonly templatesRepository: TemplatesRepository) {}

  async list(filter: ListTemplatesReqDTO) {
    const qb = this.templatesRepository.createQueryBuilder("tl");
    const { searchQuery, ...otherFilters } = filter;
    if (filter.languages) {
      qb.where("tl.language IN (:...languages)");
    }
    if (filter.templateTypes) {
      qb.where("tl.templateType IN (:...templateTypes)");
    }
    if (searchQuery) {
      qb.where("tl.name LIKE :searchQuery", { searchQuery: `%${searchQuery}%` });
    }
    if (filter.skip) {
      qb.skip(filter.skip);
    }
    if (filter.take) {
      qb.take(filter.take);
    }
    if (filter.sort) {
      qb.orderBy(filter.sort, filter.order === "ASC" ? "ASC" : "DESC");
    }
    return qb.setParameters(otherFilters).getManyAndCount();
  }

...
}

This is the test:

import { SinonStub, createSandbox, restore, stub } from "sinon";
import * as typeorm from "typeorm";

describe("TemplatesService", () => {
  let service: TemplatesService;
  let repo: TemplatesRepository;

  const sandbox = createSandbox();
  const connectionStub = sandbox.createStubInstance(typeorm.Connection);
  const templatesRepoStub = sandbox.createStubInstance(TemplatesRepository);
  const queryBuilderStub = sandbox.createStubInstance(typeorm.SelectQueryBuilder);
  stub(typeorm, "createConnection").resolves((connectionStub as unknown) as typeorm.Connection);
  connectionStub.getCustomRepository
    .withArgs(TemplatesRepository)
    .returns((templatesRepoStub as unknown) as TemplatesRepository);

  beforeAll(async () => {
    const builder: TestingModuleBuilder = Test.createTestingModule({
      imports: [
        TypeOrmModule.forRoot({
          type: "postgres",
          database: "test",
          entities: [Template],
          synchronize: true,
          dropSchema: true
        })
      ],
      providers: [ApiGuard, TemplatesService, TemplatesRepository],
      controllers: []
    });
    const module = await builder.compile();

    service = module.get<TemplatesService>(TemplatesService);
    repo = module.get<TemplatesRepository>(TemplatesRepository);
  });

  beforeEach(async () => {
    // do something
  });

  afterEach(() => {
    sandbox.restore();
    restore();
  });

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


  describe("list", () => {
    let fakeCreateQueryBuilder;

    it("should return records", async () => {
      stub(queryBuilderStub, "skip" as any).returnsThis();
      stub(queryBuilderStub, "take" as any).returnsThis();
      stub(queryBuilderStub, "sort" as any).returnsThis();
      stub(queryBuilderStub, "setParameters" as any).returnsThis();
      stub(queryBuilderStub, "getManyAndCount" as any).resolves([
        templatesRepoMocksListSuccess,
        templatesRepoMocksListSuccess.length
      ]);
      fakeCreateQueryBuilder = stub(repo, "createQueryBuilder" as any).returns(queryBuilderStub);
      const [items, totalCount] = await service.list({});

      expect(fakeCreateQueryBuilder.calledOnce).toBe(true);
      expect(fakeCreateQueryBuilder.calledOnce).toBe(true);
      expect(items.length).toBeGreaterThan(0);
      expect(totalCount).toBeGreaterThan(0);
    });
  });
});

cheers!

like image 178
Orhaan Avatar answered Nov 07 '22 02:11

Orhaan