Allen Kim
Allen Kim

Reputation: 59

What would be a proper way to test TypeORM's QueryBuilder chaining methods?

This is users.service.ts

@Injectable()
export class UserService {
  constructor(
    @InjectRepository(User) private readonly users: Repository<User>,
    @InjectRepository(Verification)
    private readonly verifications: Repository<Verification>,
    private readonly jwtService: JwtService,
    private readonly mailService: MailService,
  ) {}

  async findById(id: number): Promise<UserProfileOutput> {
    try {
      const user = await this.users.findOneOrFail({ id });
      return {
        ok: true,
        user: user,
      };
    } catch (error) {
      return { ok: false, error: 'User Not Found' };
    }
  }

I'm using TypeORM's Repository API(e.g. users.findOne, users.find, users.delete, etc). And The below file is a test file to test "users.service".

This is "users.service.spec.ts"

const mockRepository = () => ({
  findOne: jest.fn(),
  findOneOrFail: jest.fn(),
  save: jest.fn(),
  create: jest.fn(),
  delete: jest.fn(),
});

const mockJwtService = {
  sign: jest.fn(() => 'signed-token'),
  verify: jest.fn(),
};

const mockMailService = () => ({
  sendVerificationEmail: jest.fn(),
});

type MockRepository<T = any> = Partial<Record<keyof Repository<T>, jest.Mock>>;

describe('UserService', () => {
  let service: UserService;
  let usersRepository: MockRepository<User>;
  let verificationsRepository: MockRepository<Verification>;
  let mailService: MailService;
  let jwtService: JwtService;

  beforeEach(async () => {
    const module = await Test.createTestingModule({
      providers: [
        UserService,
        {
          provide: getRepositoryToken(User),
          useValue: mockRepository(),
        },
        {
          provide: getRepositoryToken(Verification),
          useValue: mockRepository(),
        },
        {
          provide: JwtService,
          useValue: mockJwtService,
        },
        {
          provide: MailService,
          useValue: mockMailService(),
        },
      ],
    }).compile();
    service = module.get<UserService>(UserService);
    mailService = module.get<MailService>(MailService);
    jwtService = module.get<JwtService>(JwtService);
    usersRepository = module.get(getRepositoryToken(User));
    verificationsRepository = module.get(getRepositoryToken(Verification));
  });

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

  describe('findById', () => {
    it('should fail if user is not found', async () => {
      usersRepository.findOneOrFail.mockRejectedValue(new Error());
      const result = await service.findById(1);
      expect(result).toEqual({
        ok: false,
        error: 'User Not Found',
      });
    });

    it('should find an existing user', async () => {
      const findByIdArgs = {
        id: 1,
      };
      usersRepository.findOneOrFail.mockResolvedValue(findByIdArgs);
      const result = await service.findById(1);
      expect(result).toEqual({
        ok: true,
        user: findByIdArgs,
      });
    });
  });

What if I switch to use TypeORM's querybuilder method.

async findByIdWithQueryBuilder(id: number): Promise<UserProfileOutput> {
    try {
      const user = await this.users
        .createQueryBuilder('user')
        .where('bundle.id = :id', { id })
        .getOneOrFail();

      return {
        ok: true,
        user,
      };
    } catch (error) {
      return { ok: false, error: 'User Not Found' };
    }
  }

The question is How can I mock QueryBuilder's chaining methods?

I tried

const mockRepository = () => ({
  findOne: jest.fn(),
  findOneOrFail: jest.fn(),
  save: jest.fn(),
  create: jest.fn(),
  delete: jest.fn(),
  createQueryBuilder: jest.fn(() => ({
    delete: () => jest.fn().mockReturnThis(),
    innerJoinAndSelect: () => jest.fn().mockReturnThis(),
    innerJoin: () => jest.fn().mockReturnThis(),
    leftJoinAndSelect: () => jest.fn().mockReturnThis(),
    leftJoin: () => jest.fn().mockReturnThis(),
    from: () => jest.fn().mockReturnThis(),
    where: () => jest.fn().mockReturnThis(),
    orWhere: () => jest.fn().mockReturnThis(),
    andWhere: () => jest.fn().mockReturnThis(),
    execute: () => jest.fn().mockReturnThis(),
    orderBy: () => jest.fn().mockReturnThis(),
    take: () => jest.fn().mockReturnThis(),
    skip: () => jest.fn().mockReturnThis(),
    getOne: () => jest.fn(),
    getMany: () => jest.fn(),
    getManyAndCount: () => jest.fn(),
  })),
});

enter image description here

I got stuck here...

What would be the proper way to mock the chaining methods of QueryBuilder api?

Upvotes: 3

Views: 4242

Answers (1)

Islam Salem
Islam Salem

Reputation: 36

you may use jest.spyOn for the QueryBuilder prototype like this:

import { QueryBuilder } from 'typeorm';

describe('Name of the group', () => {
    it('should do something', () => {
      const queryBuilder: any = {
        into: jest.fn().mockReturnThis(),
        values: jest.fn().mockReturnThis(),
        onConflict: jest.fn().mockReturnThis(),
        setParameter: jest.fn().mockReturnThis(),
        execute: jest.fn().mockRejectedValueOnce(new Error('db Error')),
        // execute: jest.fn().mockResolvedValueOnce({ res: 'mockRes' }),
      };
      jest
        .spyOn(QueryBuilder.prototype, 'insert')
        .mockReturnValueOnce(queryBuilder);
    });
});

function usage


export async function doQuery(input: any){
  const queryRunner = await Entity.createQueryRunner();
  await queryRunner.manager
  .createQueryBuilder()
  .insert()
  .into(Entity)
  .values(input)
  .onConflict(
    `("id") DO UPDATE SET "date" = :date`
  )
  .setParameter('date', date)
  .execute();
}

Upvotes: 2

Related Questions