Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[NEW TEST] Sequelize transactions #1985

Open
1 task done
c-kirkeby opened this issue Mar 6, 2023 · 1 comment
Open
1 task done

[NEW TEST] Sequelize transactions #1985

c-kirkeby opened this issue Mar 6, 2023 · 1 comment
Assignees
Labels
enhancement New feature or request

Comments

@c-kirkeby
Copy link

Is there an existing issue for this?

  • I have searched the existing issues

Feature Test To Be Requested

The sequelize-sample is really awesome, but we're using Sequelize Transactions in our codebase and by doing so we have to mock the Sequelize transaction() method or Nest will throw dependency errors:

Nest can't resolve dependencies of the CatService (CatEntityRepository, ?). Please make sure that the argument Sequelize at index [5] is available in the RootTestModule context.

Potential solutions:
- Is RootTestModule a valid NestJS module?
- If Sequelize is a provider, is it part of the current RootTestModule?
- If Sequelize is exported from a separate @Module, is that module imported within RootTestModule?
  @Module({
    imports: [ /* the Module containing Sequelize */ ]
  })

I've looked through the NestJS documentation and Discord but I couldn't find any good examples of how to do this because if I mock the Sequelize transaction like this, I don't get the value from my service call:

        {
          provide: Sequelize,
          useValue: {
            transaction: jest.fn(() => Promise.resolve()),
          },
        },

The Nest docs state to use a helper factory class but I couldn't find a good example of this either.

@c-kirkeby c-kirkeby added the enhancement New feature or request label Mar 6, 2023
@1111mp
Copy link

1111mp commented Jul 11, 2023

Is this solution correct?
I don't think I'm writing code for automated testing, just trying to piece together code that doesn't report errors. : (

users.service.ts:

@Injectable()
export class UsersService {
  constructor(
    private readonly sequelize: Sequelize,
    @InjectModel(User)
    private readonly userModel: typeof User,
    @Inject(IORedisKey) private readonly redisClient: Redis,
  ) {}

  // ......

  async getUserModel(id: number): Promise<User> {
    return this.userModel.findOne({
      attributes: { exclude: ['pwd'] },
      where: { id },
    });
  }

  async findByAccount(account: string): Promise<User.UserInfo | null> {
    const trans = await this.sequelize.transaction();
    const user = await this.userModel.findOne({
      where: {
        account,
      },
      transaction: trans,
    });

    if (!user) return null;

    const role = await user.$get('role', { transaction: trans });
    const permissions = (
      await role.$get('permissions', { transaction: trans })
    ).map(({ id, name, desc }) => ({
      id,
      name,
      desc,
    }));

    await trans.commit();

    const { id: roleId, name: roleName, desc: roleDesc } = role;

    return {
      ...user.toJSON(),
      roleId,
      roleName,
      roleDesc,
      permissions,
    };
  }

 // ......
}

users.service.spec.ts:

import { Test, TestingModule } from '@nestjs/testing';
import { getModelToken } from '@nestjs/sequelize';
import { UsersService } from './users.service';
import { User as UserModel } from './models/user.model';
import { IORedisKey } from 'src/common/redis/redis.module';

import type { Redis } from 'ioredis';
import { Sequelize } from 'sequelize-typescript';

const testUser = {
  id: 10007,
  account: '176******',
  avatar: 'avatar.png',
  email: '[email protected]',
  regisTime: '2023-01-28 12:16:06',
  updateTime: '2023-01-29 14:01:35',
};

const testRole = {
  id: 5,
  name: 'admin',
  desc: '管理员',
};

const testPermissions = {
  id: 1,
  name: 'userDel',
  desc: 'Delete user',
};

describe('UsersService', () => {
  let service: UsersService,
    model: typeof UserModel,
    redisClient: Redis,
    sequelize: Sequelize;

  beforeEach(async () => {
    const module: TestingModule = await Test.createTestingModule({
      providers: [
        UsersService,
        {
          provide: Sequelize,
          useValue: {
            transaction: jest.fn(),
          },
        },
        {
          provide: getModelToken(UserModel),
          useValue: {
            findOne: jest.fn(),
            create: jest.fn(() => testUser),
          },
        },
        { provide: IORedisKey, useValue: {} },
      ],
    }).compile();

    service = module.get<UsersService>(UsersService);
    model = module.get<typeof UserModel>(getModelToken(UserModel));
    redisClient = module.get<Redis>(IORedisKey);
    sequelize = module.get<Sequelize>(Sequelize);
  });

  afterEach(() => {
    jest.restoreAllMocks();
  });

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

  it('should get a single user by the method named getUserModel', () => {
    const findSpy = jest.spyOn(model, 'findOne');

    expect(service.getUserModel(10007));
    expect(findSpy).toBeCalledWith({
      attributes: { exclude: ['pwd'] },
      where: { id: 10007 },
    });
  });

  test('the method named findByAccount', async () => {
    // mock transaction
    const commit = jest.fn();
    const transaction = jest.fn(async () => ({ commit }) as any);
    const transSpy = jest
      .spyOn(sequelize, 'transaction')
      .mockImplementation(transaction);

    const permissionsStub = jest.fn(() => ({
        map: jest.fn(() => [testPermissions]),
      })),
      roleStub = jest.fn((key: string) => ({
        ...testRole,
        $get: permissionsStub,
      }));
    const findSpy = jest.spyOn(model, 'findOne').mockReturnValue({
      $get: roleStub,
      toJSON: jest.fn(() => testUser),
    } as any);

    const retVal = await service.findByAccount('176********');

    expect(transSpy).toBeCalledTimes(1);
    expect(findSpy).toBeCalledWith({
      where: { account: '176********' },
      transaction: await transaction(),
    });
    expect(commit).toBeCalledTimes(1);

    const { id: roleId, name: roleName, desc: roleDesc } = testRole;

    expect(retVal).toEqual({
      ...testUser,
      roleId,
      roleName,
      roleDesc,
      permissions: [testPermissions],
    });
  });
});
image

These codes look kind of stupid ...... Can anyone suggest the correct solution (or any idea).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

No branches or pull requests

3 participants