Aliheym / typeorm-transactional

A Transactional Method Decorator for TypeORM that uses Async Local Storage or cls-hooked to handle and propagate transactions between different repositories and service methods.
MIT License
201 stars 27 forks source link

How can I mocking addTransactionalDataSources() #40

Closed sweatpotato13 closed 9 months ago

sweatpotato13 commented 1 year ago

I want to write test code without DB Connect so I tried mocking typeorm

When I tried to write test code below

/* eslint-disable max-nested-callbacks */
import { CqrsModule } from "@nestjs/cqrs";
import { Test, TestingModule } from "@nestjs/testing";

import { CommandHandlers } from "../domain/commands/handlers";
import { QueryHandlers } from "../domain/queries/handlers";
import { FaucetService } from "./faucet.service";
import { getRepositoryToken } from "@nestjs/typeorm";
import { Faucet } from "@src/shared/entities";
import { HealthCheckResponseDto, RequestFaucetDto, RequestFaucetResponseDto } from "../domain/dtos";
import { initializeTransactionalContext } from "typeorm-transactional";
import { addTransactionalDataSource, getTransactionalContext } from "typeorm-transactional/dist/common";

describe("FaucetService", () => {
    let faucetService: FaucetService;

    jest.mock('typeorm-transactional', () => ({
        Transactional: () => () => ({}),
    }));

    const mockFaucetRepository = {
        save: jest.fn(),
        findOne: jest.fn()
    };

    beforeEach(async () => {
        const transactionalContext = getTransactionalContext();
        if (!transactionalContext) {
            initializeTransactionalContext();
        }

        const module: TestingModule = await Test.createTestingModule({
            imports: [CqrsModule],
            providers: [FaucetService, ...CommandHandlers, ...QueryHandlers,
                {
                    provide: getRepositoryToken(Faucet),
                    useValue: mockFaucetRepository,
                },
            ]
        }).compile();
        await module.init();
        faucetService = module.get<FaucetService>(FaucetService);

    });

    describe("healthCheck", () => {
        it("should return HealthCheckResponseDto", async () => {
            const resp = HealthCheckResponseDto.of({ result: "HealthCheck :)" });
            expect(await faucetService.healthCheck()).toStrictEqual(resp);
        });
    });

    describe("requestFaucet", () => {
        it("should return 'RequestFaucetResponseDto", async () => {
            const resp = RequestFaucetResponseDto.of({ result: "done" });
            const mockBody = RequestFaucetDto.of({ address: "5FvBpTg5eoowM87vvBeiRmFUPvVPAZyVbkE9GrYhb9Fw6nGZ", amount: 1 });
            const mockRemoteAddress = "127.0.0.1";

            expect(await faucetService.requestFaucet(mockBody, mockRemoteAddress)).toStrictEqual(resp);
        });
    });
});

It comes error like this

  No data sources defined in your app ... please call addTransactionalDataSources() before application start

Any solution about this?

happnz commented 1 year ago

Since your test does not operate with database and you mocked Transactional annotation, you probably don't need to initialize transactional context. Try removing first 4 lines in your beforeEach block.

adamwdennis commented 10 months ago

Has anybody figured out how to mock @Transactional()?

sweatpotato13 commented 9 months ago

I just add this code and works

jest.mock("typeorm-transactional", () => ({
    Transactional: () => () => ({}),
    runOnTransactionCommit: () => () => ({}),
    runOnTransactionRollback: () => () => ({}),
    runOnTransactionComplete: () => () => ({})
}));
adamwdennis commented 9 months ago

@sweatpotato13 thanks for the reply... I tried your suggested code, and I still get the following:

No storage driver defined in your app ... please call initializeTransactionalContext() before application start.

      31 |
      32 |   beforeEach(async () => {
    > 33 |     const transactionalContext = getTransactionalContext();
         |                                                         ^
      34 |     if (!transactionalContext) {
      35 |       initializeTransactionalContext();
      36 |     }
adamwdennis commented 9 months ago

@sweatpotato13 and for anyone else in my situation...

UPDATE - Great news! I moved the jest.mock to be outside of the top-level describe block, just below my import statements, and added a mock for initializeTransactionalContext and it worked!

In this example, I have a 'MyController' which has a createAndSave endpoint with @Transactional() decorator above it.

import { initializeTransactionalContext } from "typeorm-transactional";
import { Test, TestingModule } from "@nestjs/testing";
import { getRepositoryToken } from "@nestjs/typeorm";
import { MyService } from "./my.service";
import { MyController } from "./my.controller";
import { MyEntity } from "./my.entity";

jest.mock("typeorm-transactional", () => ({
  Transactional: () => () => ({}),
  runOnTransactionCommit: () => () => ({}),
  runOnTransactionRollback: () => () => ({}),
  runOnTransactionComplete: () => () => ({}),
  initializeTransactionalContext: () => ({}),
}));

describe("MyController", () => {
  let myController: MyController;
  let myService: MyService;
  let app: TestingModule;

  const mockMyRepository = jest.fn(() => ({
    findOne: jest.fn(),
    find: jest.fn(),
    // etc.
  }));

  beforeEach(async () => {
    initializeTransactionalContext();

    app = await Test.createTestingModule({
      controllers: [MyController],
      providers: [
        MyService,
        {
          provide: getRepositoryToken(MyEntity),
          useValue: mockMyRepository,
        },
      ],
    }).compile();
    await app.init();

    myController = app.get<MyController>(MyController);
    myService = app.get<MyService>(MyService);
  });

  const testEntity: MyEntity = {
    id: "1",
    name: "my1",
  };

  beforeEach(() => {
    jest.clearAllMocks();
  });

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

  describe("createAndSave", () => {
    it("should create a My record", async () => {
      jest.spyOn(myService, "createAndSave").mockResolvedValueOnce(testEntity);
      const result = await myController.createOne(testEntity);
      expect(result).toMatchObject(testEntity);
    });
  });
});