Hiro
Hiro

Reputation: 578

How to use jest.spyOn with NestJS Transaction code on Unit Test

NestJS provides a sample Transaction code on (https://docs.nestjs.com/techniques/database#transactions), and I now would like to create Unit test script against the code. Here are the some dependent files:

@Entity()
export class User {
  @PrimaryGeneratedColumn()
  id: number;

  @Column({type: 'text', name: 'first_name'})
  firstName: string;

  @Column({type: 'text', name: 'last_name'})
  lastName: string;

  @Column({name: 'is_active', default: true})
  isActive: boolean;
}
@Injectable()
export class UsersService {
  constructor(
    private connection: Connection
  ) {}

  async createMany(users: User[]): User[] {
    const queryRunner = this.connection.createQueryRunner();

    await queryRunner.connect();
    await queryRunner.startTransaction();
    try {
      const user1 = await queryRunner.manager.save(users[0]);

      await queryRunner.commitTransaction();

      return [user1];
    } catch (err) {
      // since we have errors lets rollback the changes we made
      await queryRunner.rollbackTransaction();
    } finally {
      // you need to release a queryRunner which was manually instantiated
      await queryRunner.release();
    }
  }
}

Here is the unit test script. I am close to accomplish but still I am getting undefined from await queryRunner.manager.save(users[0]) with the jest.spyOn setup below:

describe('UsersService', () => {
  let usersService: UsersService;
  let connection: Connection;

  class ConnectionMock {
    createQueryRunner(mode?: "master" | "slave"): QueryRunner {
      const qr = {
        manager: {},
      } as QueryRunner;
      qr.manager;
      Object.assign(qr.manager, {
        save: jest.fn()
      });
      qr.connect = jest.fn();
      qr.release = jest.fn();
      qr.startTransaction = jest.fn();
      qr.commitTransaction = jest.fn();
      qr.rollbackTransaction = jest.fn();
      qr.release = jest.fn();
      return qr;
    }
  }

  beforeEach(async () => {
    const module: TestingModule = await Test.createTestingModule({
      providers: [UsersService, {
        provide: Connection,
        useClass: ConnectionMock,
      }],
    }).compile();

    usersService = module.get<UsersService>(UsersService);
    connection = module.get<Connection>(Connection);
  });

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

  it('should return expected results after user create', async () => {
    const user1: User = { id: 1, firstName: 'First', lastName: 'User', isActive: true };
    const queryRunner = connection.createQueryRunner();
    // I would like to make the following spyOn work
    jest.spyOn(queryRunner.manager, 'save').mockResolvedValueOnce(user1);
    expect(await appService.createMany([user1])).toEqual([user1]);
  });
});

Upvotes: 1

Views: 8538

Answers (1)

hoangdv
hoangdv

Reputation: 16127

connection.createQueryRunner(); will return difference instance of QueryRunner.

This mean queryRunner of const queryRunner = connection.createQueryRunner(); will not reference to queryRunner in const queryRunner = this.connection.createQueryRunner(); of createMany function.

Then your mock jest.spyOn(queryRunner.manager, 'save').mockResolvedValueOnce(user1); does not make sense.

Keep it simple, make QueryRunner as a "global" variable, then binding it to ConnectionMock, this mean we will have the same "variable" when we call createQueryRunner.

Updated beforeEach content, ConnectionMock class.

describe('UsersService', () => {
  let usersService: UsersService;
  let connection: Connection;

  const qr = {
    manager: {},
  } as QueryRunner;

  class ConnectionMock {
    createQueryRunner(mode?: "master" | "slave"): QueryRunner {
      return qr;
    }
  }

  beforeEach(async () => {
    // reset qr mocked function
    Object.assign(qr.manager, {
      save: jest.fn()
    });
    qr.connect = jest.fn();
    qr.release = jest.fn();
    qr.startTransaction = jest.fn();
    qr.commitTransaction = jest.fn();
    qr.rollbackTransaction = jest.fn();
    qr.release = jest.fn();

    const module: TestingModule = await Test.createTestingModule({
      providers: [UsersService, {
        provide: Connection,
        useClass: ConnectionMock,
      }],
    }).compile();

    usersService = module.get<UsersService>(UsersService);
    connection = module.get<Connection>(Connection);
  });

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

  it('should return expected results after user create', async () => {
    const user1: User = { id: 1, firstName: 'First', lastName: 'User', isActive: true };
    const queryRunner = connection.createQueryRunner();
    // I would like to make the following spyOn work
    jest.spyOn(queryRunner.manager, 'save').mockResolvedValueOnce(user1);
    expect(await appService. createMany([user1])).toEqual([user1]);
  });
});

Upvotes: 2

Related Questions