Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to mock chained function calls using jest?

I am testing the following service:

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

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

  public 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;
  }
}

My repository mock:

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

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

I want to mock the call to the tripRepository.createQueryBuilder().innerJoinAndSelect().where().select().getOne();

First question, should I mock the chained calls here because I assume that it should already be tested in Typeorm.

Second, if I want to mock the parameters passed to each chained call and finally also mock the return value, how can I go about it?

like image 682
Abhyudit Jain Avatar asked Jun 18 '19 08:06

Abhyudit Jain


People also ask

How do you mock chain methods in Jest?

You can use mockFn. mockReturnThis() to do this. const client = { items: () => { return client; }, type: (name: string) => { return client; }, toObservable: () => { return client; }, subscribe: handler => { handler(); return client; } }; export { client };

How do you mock a particular function in Jest?

Function mock using jest. The simplest and most common way of creating a mock is jest. fn() method. If no implementation is provided, it will return the undefined value. There is plenty of helpful methods on returned Jest mock to control its input, output and implementation.

How do you mock a function in Jest react?

Let's refactor the test to use this approach: test("Should render character name", async () => { const mock = jest. spyOn(data, "getCharacter"). mockResolvedValue("Bob"); render(<Hello id={1} />); expect(await screen.


2 Answers

I had a similar need and solved using the following approach.

This is the code I was trying to test. Pay attention to the createQueryBuilder and all the nested methods I called.

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;

Now, take a look at the test I wrote that simulates the chained calls of the above methods.

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,
  );
});
like image 163
Guilherme De Jesus Rafael Avatar answered Oct 13 '22 19:10

Guilherme De Jesus Rafael


Guilherme's answer is totally right. I just wanted to offer a modified approach that might apply to more test cases, and in TypeScript. Instead of defining your chained calls as (), you can use a jest.fn, allowing you to make more assertions. 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`)
like image 35
data princess Avatar answered Oct 13 '22 17:10

data princess