Node.js 如何使用jest模拟链式函数调用?

Node.js 如何使用jest模拟链式函数调用?,node.js,typescript,jestjs,nestjs,typeorm,Node.js,Typescript,Jestjs,Nestjs,Typeorm,我正在测试以下服务: @Injectable() 出口类TripService{ 专用只读记录器=新记录器(“TripService”); 建造师( @注入存储库(TripEntity) 私有存储库:存储库 ) {} 公共异步showTrip(clientId:string,tripId:string):承诺{ const trip=wait this.tripRepository .createQueryBuilder(“trips”) .innerJoinAndSelect('trips.d

我正在测试以下服务:

@Injectable()
出口类TripService{
专用只读记录器=新记录器(“TripService”);
建造师(
@注入存储库(TripEntity)
私有存储库:存储库
) {}
公共异步showTrip(clientId:string,tripId:string):承诺{
const trip=wait this.tripRepository
.createQueryBuilder(“trips”)
.innerJoinAndSelect('trips.driver','driver','driver.clientId=:clientId',{clientId})
.where({id:tripId})
.选择([
“trips.id”,
“行程、距离”,
“trips.sourceAddress”,
“trips.destinationAddress”,
“trips.startTime”,
“trips.endTime”,
“trips.createdAt”
])
.getOne();
如果(!行程){
抛出新的HttpException('Trip not found',HttpStatus.not_found');
}
回程;
}
}
我的存储库模拟:

export const repositoryMockFactory: () => MockType<Repository<any>> = jest.fn(() => ({
    findOne: jest.fn(entity => entity),
    findAndCount: jest.fn(entity => entity),
    create: jest.fn(entity => entity),
    save: jest.fn(entity => entity),
    update: jest.fn(entity => entity),
    delete: jest.fn(entity => entity),
    createQueryBuilder: jest.fn(() => ({
        delete: jest.fn().mockReturnThis(),
        innerJoinAndSelect: jest.fn().mockReturnThis(),
        innerJoin: jest.fn().mockReturnThis(),
        from: jest.fn().mockReturnThis(),
        where: jest.fn().mockReturnThis(),
        execute: jest.fn().mockReturnThis(),
        getOne: jest.fn().mockReturnThis(),
    })),
}));
export const repositoryMockFactory:()=>MockType=jest.fn(()=>({
findOne:jest.fn(entity=>entity),
findAndCount:jest.fn(entity=>entity),
create:jest.fn(entity=>entity),
保存:jest.fn(entity=>entity),
更新:jest.fn(entity=>entity),
删除:jest.fn(entity=>entity),
createQueryBuilder:jest.fn(()=>({
删除:jest.fn().mockReturnThis(),
innerJoinAndSelect:jest.fn().mockReturnThis(),
innerJoin:jest.fn().mockReturnThis(),
from:jest.fn().mockReturnThis(),
其中:jest.fn().mockReturnThis(),
execute:jest.fn().mockReturnThis(),
getOne:jest.fn().mockReturnThis(),
})),
}));
My tripService.spec.ts:

import { Test, TestingModule } from '@nestjs/testing';
import { TripService } from './trip.service';
import { MockType } from '../mock/mock.type';
import { Repository } from 'typeorm';
import { TripEntity } from './trip.entity';
import { getRepositoryToken } from '@nestjs/typeorm';
import { repositoryMockFactory } from '../mock/repositoryMock.factory';
import { DriverEntity } from '../driver/driver.entity';
import { plainToClass } from 'class-transformer';

describe('TripService', () => {
  let service: TripService;
  let tripRepositoryMock: MockType<Repository<TripEntity>>;
  let driverRepositoryMock: MockType<Repository<DriverEntity>>;

  beforeEach(async () => {
    const module: TestingModule = await Test.createTestingModule({
      providers: [
        TripService,
        { provide: getRepositoryToken(DriverEntity), useFactory: repositoryMockFactory },
        { provide: getRepositoryToken(TripEntity), useFactory: repositoryMockFactory },
      ],
    }).compile();

    service = module.get<TripService>(TripService);
    driverRepositoryMock = module.get(getRepositoryToken(DriverEntity));
    tripRepositoryMock = module.get(getRepositoryToken(TripEntity));
  });

  it('should be defined', () => {
    expect(service).toBeDefined();
    expect(driverRepositoryMock).toBeDefined();
    expect(tripRepositoryMock).toBeDefined();
  });

  describe('TripService.showTrip()', () => {
    const trip: TripEntity = plainToClass(TripEntity, {
      id: 'one',
      distance: 123,
      sourceAddress: 'one',
      destinationAddress: 'one',
      startTime: 'one',
      endTime: 'one',
      createdAt: 'one',
    });
    it('should show the trip is it exists', async () => {
      tripRepositoryMock.createQueryBuilder.mockReturnValue(trip);
      await expect(service.showTrip('one', 'one')).resolves.toEqual(trip);
    });
  });
});
从'@nestjs/testing'导入{Test,TestingModule};
从“/trip.service”导入{TripService};
从“../mock/mock.type”导入{MockType};
从“typeorm”导入{Repository};
从“./trip.entity”导入{TripEntity};
从'@nestjs/typeorm'导入{getRepositoryToken};
从“../mock/repositoryMock.factory”导入{repositoryMockFactory};
从“../driver/driver.entity”导入{DriverEntity};
从“类转换器”导入{plainToClass};
描述('TripService',()=>{
让服务:TripService;
让tripRepositoryMock:MockType;
let driverRepositoryMock:MockType;
beforeach(异步()=>{
常量模块:TestingModule=等待测试。createTestingModule({
供应商:[
TripService,
{提供:getRepositoryToken(DriverEntity),useFactory:repositoryMockFactory},
{提供:getRepositoryToken(TripEntity),useFactory:repositoryMockFactory},
],
}).compile();
服务=模块.get(TripService);
driverRepositoryMock=module.get(getRepositoryToken(DriverEntity));
tripRepositoryMock=module.get(getRepositoryToken(TripEntity));
});
它('应该定义',()=>{
expect(service.toBeDefined();
expect(driverRepositoryMock.toBeDefined();
expect(tripRepositoryMock).toBeDefined();
});
描述('TripService.showTrip()',()=>{
常量trip:TripEntity=plainToClass(TripEntity{
id:'一',
距离:123,
sourceAddress:'一',
目的地地址:“一”,
开始时间:'一',
结束时间:“一”,
createdAt:'一',
});
它('应该显示它是否存在',async()=>{
tripRepositoryMock.createQueryBuilder.mockReturnValue(trip);
等待期望(service.showTrip('one','one'))。解决。toEqual(trip);
});
});
});
我想模拟对
tripRepository.createQueryBuilder().innerJoinAndSelect().where().select().getOne()的调用

第一个问题,我是否应该在这里模拟链式调用,因为我假设它应该已经在Typeorm中进行了测试


第二,如果我想模拟传递给每个链式调用的参数,并最终模拟返回值,我该怎么做呢?

我也有类似的需要,并使用以下方法解决

这就是我试图测试的代码。请注意
createQueryBuilder
和我调用的所有嵌套方法

const reactions = await this.reactionEntity
  .createQueryBuilder(TABLE_REACTIONS)
  .select('reaction')
  .addSelect('COUNT(1) as count')
  .groupBy('content_id, source, reaction')
  .where(`content_id = :contentId AND source = :source`, {
    contentId,
    source,
  })
  .getRawMany<GetContentReactionsResult>();

return reactions;

吉尔赫姆的回答完全正确。我只是想提供一种经过修改的方法,可以应用于更多的测试用例,并且使用TypeScript。您可以使用
jest.fn
,而不是将链式调用定义为
()
,从而允许您进行更多断言。e、 g

/* eslint-disable  @typescript-eslint/no-explicit-any */
const createQueryBuilder: any = {
  select: jest.fn().mockImplementation(() => {
    return createQueryBuilder
  }),
  addSelect: jest.fn().mockImplementation(() => {
    return createQueryBuilder
  }),
  groupBy: jest.fn().mockImplementation(() => {
    return createQueryBuilder
  }),
  where: jest.fn().mockImplementation(() => {
    return createQueryBuilder
  }),
  getRawMany: jest
    .fn()
    .mockImplementationOnce(() => {
      return FILTERED_REACTIONS
    })
    .mockImplementationOnce(() => {
      return SOMETHING_ELSE
    }),
}

/* run your code */

// then you can include an assertion like this:
expect(createQueryBuilder.groupBy).toHaveBeenCalledWith(`some group`)

我想说的是,您最好只使用具有特定节点和配置的测试数据库,以便您的测试包括其中的所有sql事务管理。在我看来,模拟TypeOrm SQL交互似乎是测试您的服务的次优方法,这在开发时间上也相当昂贵。@zenbeni我将在e2e测试中使用测试数据库进行测试。这只是一个单元测试,所以我不想使用数据库进行测试,因为它会降低测试速度并增加与数据库的紧密耦合。这项服务背后的含义是什么?它似乎是存储库的包装器。如果您想进行单元测试,那么应该对业务逻辑进行单元测试,而不是持久性层。TripRepository是从库中提交的还是您自己的?您的测试方法似乎更像是一个功能测试,您应该为它设置一个实际的测试数据库。此外,我认为您不应该为此服务创建单元测试。此服务依赖于数据库。模拟整个数据库连接是没有意义的。相反,这个服务是您对其他服务的依赖,应该是被模仿的服务。对于其他服务,您应该编写测试:“假设我的TripService给了我一次旅行,那么我希望这会发生”和“假设我得到一个异常,我希望它以这种方式处理”您不应该模拟存储库上的每个调用。这是您的底层持久层。假设它正在工作。@k0pernikus此服务不仅仅是一个包装器,它在查询前后都有业务逻辑。我只是举个例子。真正的问题是模拟链式函数调用。这个。有很多问题
/* eslint-disable  @typescript-eslint/no-explicit-any */
const createQueryBuilder: any = {
  select: jest.fn().mockImplementation(() => {
    return createQueryBuilder
  }),
  addSelect: jest.fn().mockImplementation(() => {
    return createQueryBuilder
  }),
  groupBy: jest.fn().mockImplementation(() => {
    return createQueryBuilder
  }),
  where: jest.fn().mockImplementation(() => {
    return createQueryBuilder
  }),
  getRawMany: jest
    .fn()
    .mockImplementationOnce(() => {
      return FILTERED_REACTIONS
    })
    .mockImplementationOnce(() => {
      return SOMETHING_ELSE
    }),
}

/* run your code */

// then you can include an assertion like this:
expect(createQueryBuilder.groupBy).toHaveBeenCalledWith(`some group`)