如何使用笑话模拟链接的函数调用?

时间:2019-06-18 08:19:07

标签: node.js typescript jestjs nestjs typeorm

我正在测试以下服务:

@Injectable()
export class TripService {
    private readonly logger = new Logger('TripService');

    constructor(
        @InjectRepository(TripEntity)
        private tripRepository: Repository<TripEntity>,
    ) { }

    async showTrip(clientId: string, tripId: string): Promise<Partial<TripEntity>> {
        const trip = await this.tripRepository
            .createQueryBuilder('trips')
            .innerJoinAndSelect('trips.driver', 'driver', 'driver.clientId = :clientId', { clientId })
            .where({ id: tripId })
            .select([
                'trips.id',
                'trips.distance',
                'trips.sourceAddress',
                'trips.destinationAddress',
                'trips.startTime',
                'trips.endTime',
                'trips.createdAt',
            ])
            .getOne();

        if (!trip) {
            throw new HttpException('Trip not found', HttpStatus.NOT_FOUND);
        }

        return trip;
    }
}

我的存储库模拟:

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(),
    })),
}));

我的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);
    });
  });
});

我想模拟对tripRepository.createQueryBuilder().innerJoinAndSelect().where().select().getOne();的呼叫

第一个问题,我应该在这里模拟链接的调用,因为我认为应该已经在Typeorm中对其进行了测试。

第二,如果我想模拟传递给每个链接调用的参数,最后还要模拟返回值,我该怎么办?

2 个答案:

答案 0 :(得分:6)

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

这是我尝试测试的代码。注意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;

现在,看看我编写的模拟上述方法的链接调用的测试。

it('should return the reactions that match the supplied parameters', async () => {
  const PARAMS = { contentId: '1', source: 'anything' };

  const FILTERED_REACTIONS = REACTIONS.filter(
    r => r.contentId === PARAMS.contentId && r.source === PARAMS.source,
  );

  // Pay attention to this part. Here I created a createQueryBuilder 
  // const with all methods I call in the code above. Notice that I return
  // the same `createQueryBuilder` in all the properties/methods it has
  // except in the last one that is the one that return the data 
  // I want to check.
  const createQueryBuilder: any = {
    select: () => createQueryBuilder,
    addSelect: () => createQueryBuilder,
    groupBy: () => createQueryBuilder,
    where: () => createQueryBuilder,
    getRawMany: () => FILTERED_REACTIONS,
  };

  jest
    .spyOn(reactionEntity, 'createQueryBuilder')
    .mockImplementation(() => createQueryBuilder);

  await expect(query.getContentReactions(PARAMS)).resolves.toEqual(
    FILTERED_REACTIONS,
  );
});

答案 1 :(得分:1)

Guilherme 的回答完全正确。我只是想提供一种可能适用于更多测试用例和 TypeScript 的修改方法。您可以使用 (),而不是将您的链式调用定义为 jest.fn,从而允许您进行更多断言。例如,

/* 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`)