testing-nestjs
testing-nestjs copied to clipboard
[NEW TEST] Sequelize transactions
Is there an existing issue for this?
- [X] 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.
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],
});
});
});
These codes look kind of stupid ...... Can anyone suggest the correct solution (or any idea).