簡體   English   中英

NestJS TypeORM 單元測試 mocking getConnection.transaction

[英]NestJS TypeORM Unit Tests mocking getConnection.transaction

我正在為 NestJS 應用程序編寫 UT。 我有問題 mocking 甚至測試內部事務邏輯 function。

這是一段來自服務的代碼,它獲取連接並開始使用 transactionEntityManager 與數據庫交互。

我想像這個例子一樣測試 function 的內部邏輯(我有幾個這樣的例子)。

import { getConnection } from 'typeorm';
@Injectable()
export class UsersService {

    async getUsers(user: User): Promise<{ users: User[], organizations: MinimalOrg[] }> {
        const result: { users: User[], organizations: MinimalOrg[] } = { users: [], organizations: [] };
        // getConnection() is imported from 'typeorm'
        await getConnection().transaction(async (transactionEntityManager) => {
            if (!user || !Utils.isAdmin(user)) {
                throw new UnauthorizedException('You have no permission');
            }

            result.users = await transactionEntityManager
                .createQueryBuilder(User, 'user')
                .leftJoin('user.image', 'image')
                .leftJoin('user.organization', 'organization')
                .select([
                    'user.id',
                    'user.email',
                ])
                .addSelect(['organization.id', 'organization.name'])
                .addSelect(['image.id', 'image.fileName'])
                .getMany();

            result.organizations = (await transactionEntityManager
                .createQueryBuilder(Organization, 'org')
                .select(['org.id', 'org.name'])
                .getMany()) as MinimalOrg[];
        });
        return result;
    }
}

我已經嘗試在單元測試中使用這種方法,但無法使其工作。

jest.mock('typeorm',()=>({
    transaction:jest.fn()
}));

任何幫助將不勝感激

您需要先模擬getConnection() ,然后再模擬 mocking transction()本身。

const queryBuilderMock = {
  leftJoin: jest.fn().mockReturnThis(),
  select: jest.fn().mockReturnThis(),
  addSelect: jest.fn().mockReturnThis(),
  getMany: jest.fn().mockResolvedValue(['your_mocked_object_here']),
};

const entityManagerMock = { createQueryBuilder: () => queryBuilderMock };
const transactionMock = jest.fn(async passedFunction => await passedFunction(entityManagerMock));

jest.mock('typeorm', () => ({
  getConnection: () => ({ transaction: transactionMock })
}));

也不要忘記在每次測試之前清理/重置所需的模擬。

  beforeEach(async () => {
    queryBuilderMock.addSelect.mockClear();
    queryBuilderMock.select.mockClear();
    queryBuilderMock.getMany.mockClear();
    queryBuilderMock.leftJoin.mockClear();
    const module: TestingModule = await Test.createTestingModule({
      providers: [UsersService],
    }).compile();

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

經過幾天的調查和您的反饋,我通過以下步驟得出了這個解決方案:

  1. 在 devDependencies 中安裝 sqlite3(僅用於連接的內存數據庫)。
  2. 注入服務@InjectConnection() private readonly connection: Connection
  3. 用@Transaction() 裝飾方法並添加服務方法的新參數@TransactionManager() _manager? .

服務方法現在看起來像這樣:

    @Transaction()
    async getUsers(
        user: User,
        @TransactionManager() _manager?
    ): Promise<{ users: User[], organizations: MinimalOrg[] }> {
        const result: { users: User[], organizations: MinimalOrg[] } = { users: [], organizations: [] };
        const transactionEntityManager = this.connection.manager;
        if (!user || !Utils.isAdmin(user)) {
            throw new UnauthorizedException('You have no permission');
        }
        result.users = await transactionEntityManager
            .createQueryBuilder(User, 'user')
            .leftJoin('user.image', 'image')
            .leftJoin('user.organization', 'organization')
            .select([
                'user.id',
                'user.email',
            ])
            .addSelect(['organization.id', 'organization.name'])
            .addSelect(['image.id', 'image.fileName'])
            .getMany();
        result.organizations = (await transactionEntityManager
            .createQueryBuilder(Organization, 'org')
            .select(['org.id', 'org.name'])
            .getMany()) as MinimalOrg[];
        return result;
    }

在規范文件中,使用數據庫配置設置導入很重要,如下所示:

/* eslint-disable import/no-extraneous-dependencies */
import { Logger } from '@nestjs/common';
import { Test } from '@nestjs/testing';
import { getConnectionToken, TypeOrmModule, } from '@nestjs/typeorm';
import { UserRole } from 'gn-models';
import { ConnectionOptions, getConnection } from 'typeorm';

import { User } from '../models/user.entity';
import { UsersService } from './users.service';

// #region Mock
const createQueryBuilder: any = {
    createQueryBuilder: jest.fn().mockImplementation(() => createQueryBuilder),
    select: jest.fn().mockImplementation(() => createQueryBuilder),
    andWhere: jest.fn().mockImplementation(() => createQueryBuilder),
    addSelect: jest.fn().mockImplementation(() => createQueryBuilder),
    leftJoinAndSelect: jest.fn().mockImplementation(() => createQueryBuilder),
    innerJoin: jest.fn().mockImplementation(() => createQueryBuilder),
    leftJoin: jest.fn().mockImplementation(() => createQueryBuilder),
    groupBy: jest.fn().mockImplementation(() => createQueryBuilder),
    where: jest.fn().mockImplementation(() => createQueryBuilder),
    findOne: jest.fn().mockImplementation(() => null),
    save: jest.fn().mockImplementation(() => null),
    getOne: jest.fn().mockImplementation(() => null),
    getMany: jest.fn().mockImplementation(() => null),
    getRawMany: jest.fn().mockImplementation(() => null)
}
// #endregion

describe('UsersService', () => {
    let srv: UsersService;
    beforeEach(async () => {
        const moduleRef = await Test.createTestingModule({
            imports: [
                TypeOrmModule.forRoot({
                    type: "sqlite",
                    database: ":memory:",
                    dropSchema: true,
                    synchronize: true,
                    logging: false,
                    name: 'default'
                } as ConnectionOptions)
            ],
            providers: [
                UsersService,
                {
                    provide: getConnectionToken(),
                    useValue: {
                        manager: createQueryBuilder,
                        ...createQueryBuilder
                    }
                },
            ],
        }).compile();

        // Disable log error prints 
        jest.spyOn(Logger, 'error').mockReturnValue();

        srv = moduleRef.get<UsersService>(UsersService);
    });

    afterEach(async () => {
        jest.clearAllMocks();
        await getConnection().close();
    });


    it('should test getUsers', async () => {
        const userRecord = { rdp: '1', userName: 'aaa', roles: [UserRole.SuperAdmin] } as User;

        const res = await srv.getUsers(userRecord);
        expect(res).toBeTruthy();
        expect(res).toEqual({ users: null, organizations: null })
    });

    it('should test getUsers if the user is not admin', async () => {
        const userRecord = { rdp: '1', userName: 'aaa', roles: [] } as User;
        expect.assertions(1);
        const res = srv.getUsers(userRecord);
        await expect(res).rejects
            .toThrowError('You have no permission');
    });
});

請注意,管理器設置為beforeEach連接允許 mocking 測試中的實體管理器,並且仍然在服務方法const transactionEntityManager = this.connection.manager;中使用它。 .

我已經設置了await getConnection().close(); afterEach這樣每個測試將彼此獨立運行。 如果連接已經打開,jest 將無法運行具有相同連接名稱(默認)的其他測試。

我並不是說這是最好的解決方案,但在投入大量時間和精力完成這項工作后,它對我有用。

我希望它能幫助你為你的項目編寫 UT!

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM