sandbox.restore() 不会重置存根的调用计数
sandbox.restore() won't reset stub's called count
我对 Sinon/Jest 和单元测试完全陌生,所以我有点迷路了。我试图制作一个沙箱来声明其中的所有存根,但即使在使用 sandbox.restore() 之后,存根的调用计数仍会保留,因此我的测试在下一个 'it'.
中失败
我无法直接存根 TypeORM 的对象,所以我决定只使用我需要的方法创建假对象,并让 TypeORM 的 getRepository() 使用我创建的对象。
我不确定这种方法是否正确,但看起来我的测试正在运行,我可以断言调用次数及其参数,在第二个 'it' 我可以预期会抛出错误等于 'Email já cadastrado',如果我更改预期消息,则测试失败。
问题是调用次数不会在下一个 'it' 块之前重置。所以我总是在“sandbox.assert.calledOnceWithExactly(fakeConnection.getRepository, Cidades)”这一行出现错误,因为它被调用了两次(一次在前一个 'it' 上,第二次在当前块上)。
如果我删除该行,我会在 assert transaction 部分收到一个错误,因为我预计 commitTransaction 不会被调用,但它被调用了一次(在之前的 'it' 块中)。
Image of my Error
这是我的测试:
const sandbox = sinon.createSandbox();
// Simulating TypeORM's QueryRunner
const fakeQueryRunner = {
connect: sandbox.stub().returnsThis(),
startTransaction: sandbox.stub().returnsThis(),
rollbackTransaction: sandbox.stub().returnsThis(),
commitTransaction: sandbox.stub().returnsThis(),
release: sandbox.stub().returnsThis(),
manager: { save: sandbox.stub().returnsThis() },
};
// Simulating TypeORM's Connection
const fakeConnection = {
createQueryRunner: sandbox.stub().returns( fakeQueryRunner ),
getRepository: sandbox.stub().returnsThis(),
findOneOrFail: sandbox.stub().returnsThis(),
}
// I've hidden the mock of my parameters/entities (Usuarios, Senhas, Cidades) since I don't think it's needed to solve the problem.
describe('UserRepository', function () {
let userRepository;
beforeEach(async () => {
const module = await Test.createTestingModule({
providers: [
UserRepository,
],
}).compile();
userRepository = module.get<UserRepository>(UserRepository);
});
describe('signUp', function () {
beforeEach(function () {
// typeORM.getConnection() returns my object simulating a Connection.
sandbox.stub(typeorm, 'getConnection').returns( fakeConnection as unknown as typeorm.Connection )
// Stubbing this.create()
sandbox.stub(userRepository, 'create').returns( Usuarios )
});
afterEach(function () {
sandbox.restore();
});
it('successfully signs up the user', async function () {
// Simulating sucessful save transaction
fakeQueryRunner.manager.save.onCall(0).resolves(Usuarios);
fakeQueryRunner.manager.save.onCall(1).resolves(Senhas);
// Calling my method
await userRepository.signUp(mockCredentialsDto);
// First interation, this line works
sandbox.assert.calledOnceWithExactly(fakeConnection.getRepository, Cidades);
// Asserting that transaction was commited
sandbox.assert.calledOnce(fakeQueryRunner.commitTransaction);
sandbox.assert.notCalled(fakeQueryRunner.rollbackTransaction);
});
it('throws a conflic exception as username already exists', async function () {
// Simulating a reject from transaction
fakeQueryRunner.manager.save.onCall(0).rejects({ code: '23505' });
// Calling my method and catching error
try {
await userRepository.signUp(mockCredentialsDto)
} catch (err) {
expect(err).toEqual(new Error('Email já cadastrado'));
}
// Second interation, this line giver ERROR (method called twice)
sandbox.assert.calledOnceWithExactly(fakeConnection.getRepository, Cidades);
// Asserting that transaction was rolled back, this also gives an error since commitTransaction was called once (in the first 'it' block)
sandbox.assert.notCalled(fakeQueryRunner.commitTransaction);
sandbox.assert.calledOnce(fakeQueryRunner.rollbackTransaction);
});
// I will make another describe block here eventually
});
这是我正在测试的方法:
async signUp(authCredentialsDto: AuthCredentialsDto): Promise<signUpMessage> {
const { nome, email, genero, dataNascimento, profissao, organizacao, atuacao, nomeCidade, uf, senha } = authCredentialsDto;
const connection = getConnection();
const user = this.create();
const senhas = new Senhas;
const cidade = await connection.getRepository(Cidades).findOneOrFail({where: { nome: nomeCidade, uf: uf } });
// Set values
user.nome = nome;
user.email = email;
user.genero = genero;
user.dataNascimento = dataNascimento;
user.profissao = profissao;
user.organizacao = organizacao;
user.atuacao = atuacao;
user.idCidade = cidade;
const salt = await bcrypt.genSalt();
senhas.senha = await this.hashPassword(senha, salt)
senhas.idUsuario2 = user;
// Make a transaction to save the data
const queryRunner = connection.createQueryRunner();
await queryRunner.connect();
await queryRunner.startTransaction();
try {
await queryRunner.manager.save(user);
await queryRunner.manager.save(senhas);
await queryRunner.commitTransaction();
} catch (error) {
if ( error.code === '23505' ) { // Usuário repetido
await queryRunner.rollbackTransaction();
throw new ConflictException('Email já cadastrado')
} else {
await queryRunner.rollbackTransaction();
throw new InternalServerErrorException;
}
} finally {
await queryRunner.release();
}
let success: signUpMessage;
return success;
}
为了让它工作,我在 beforeEach 中声明了我的 Sinon 沙盒和对象,而不是开始我的测试。
我的测试现在看起来像这样:
describe('UserRepository', () => {
let userRepository
let sandbox
let fakeQueryRunner
let fakeConnection
let mockUser: Usuarios
beforeEach(async () => {
sandbox = sinon.createSandbox()
// Cria um objeto QueryRunner fake
fakeQueryRunner = {
connect: sandbox.stub().returnsThis(),
startTransaction: sandbox.stub().returnsThis(),
rollbackTransaction: sandbox.stub().returnsThis(),
commitTransaction: sandbox.stub().returnsThis(),
release: sandbox.stub().returnsThis(),
manager: { save: sandbox.stub().returnsThis() },
}
// Cria um objeto Connection fake (note que o stub de createQueryRunner retorna o objeto fakeQueryRunner )
fakeConnection = {
createQueryRunner: sandbox.stub().returns(fakeQueryRunner),
getRepository: sandbox.stub().returnsThis(),
findOneOrFail: sandbox.stub().returnsThis(),
}
mockUser = {
idUsuario: '1',
atuacao: 'lol',
dataNascimento: '10/10/2021',
email: 'teste@kik.com',
genero: Genero.MASCULINO,
nome: 'Teste Nome',
organizacao: 'Teste org',
profissao: 'Teste Prof',
idCidade: mockCidade,
membros: [],
senhas: mockSenha,
validatePassword: sandbox.stub().returnsThis(),
}
const module = await Test.createTestingModule({
providers: [UserRepository],
}).compile()
userRepository = module.get<UserRepository>(UserRepository)
})
afterEach(() => {
sandbox.restore()
})
describe('signUp', () => {
beforeEach(function () {
// Cria um método falso que retorna o objeto fakeConnection
sandbox.stub(typeorm, 'getConnection').returns((fakeConnection as unknown) as typeorm.Connection)
// Simula o Create de UserRepository
sandbox.stub(userRepository, 'create').returns(Usuarios)
})
it('successfully signs up the user', async () => {
// Salvar Usuário e Senha foi bem sucedido
fakeQueryRunner.manager.save.onCall(0).resolves(Usuarios)
fakeQueryRunner.manager.save.onCall(1).resolves(Senhas)
await userRepository.signUp(mockCredentialsDto)
// Verificando instanciação do repositório
const call1 = fakeConnection.getRepository.onCall(0).resolves(Cidades)
sandbox.assert.calledWith(call1, Cidades)
sandbox.assert.calledOnceWithExactly(fakeConnection.getRepository, Cidades)
// Verificando Transactions
sandbox.assert.calledOnce(fakeQueryRunner.commitTransaction)
sandbox.assert.notCalled(fakeQueryRunner.rollbackTransaction)
})
it('throws a conflic exception as username already exists', async () => {
// Houve um erro em um dos saves, resultando num rollback da transaction
fakeQueryRunner.manager.save.onCall(0).resolves(Usuarios)
fakeQueryRunner.manager.save.onCall(1).rejects({ code: '23505' })
try {
await userRepository.signUp(mockCredentialsDto)
} catch (err) {
expect(err).toEqual(new Error('Email já cadastrado'))
}
// Verificando instanciação do repositório
const call1 = fakeConnection.getRepository.onCall(0).resolves(Cidades)
sandbox.assert.calledWith(call1, Cidades)
sandbox.assert.calledOnceWithExactly(fakeConnection.getRepository, Cidades)
// Verificando Transactions
sandbox.assert.notCalled(fakeQueryRunner.commitTransaction)
sandbox.assert.calledOnce(fakeQueryRunner.rollbackTransaction)
})
})
})
我对 Sinon/Jest 和单元测试完全陌生,所以我有点迷路了。我试图制作一个沙箱来声明其中的所有存根,但即使在使用 sandbox.restore() 之后,存根的调用计数仍会保留,因此我的测试在下一个 'it'.
中失败我无法直接存根 TypeORM 的对象,所以我决定只使用我需要的方法创建假对象,并让 TypeORM 的 getRepository() 使用我创建的对象。
我不确定这种方法是否正确,但看起来我的测试正在运行,我可以断言调用次数及其参数,在第二个 'it' 我可以预期会抛出错误等于 'Email já cadastrado',如果我更改预期消息,则测试失败。
问题是调用次数不会在下一个 'it' 块之前重置。所以我总是在“sandbox.assert.calledOnceWithExactly(fakeConnection.getRepository, Cidades)”这一行出现错误,因为它被调用了两次(一次在前一个 'it' 上,第二次在当前块上)。
如果我删除该行,我会在 assert transaction 部分收到一个错误,因为我预计 commitTransaction 不会被调用,但它被调用了一次(在之前的 'it' 块中)。
Image of my Error
这是我的测试:
const sandbox = sinon.createSandbox();
// Simulating TypeORM's QueryRunner
const fakeQueryRunner = {
connect: sandbox.stub().returnsThis(),
startTransaction: sandbox.stub().returnsThis(),
rollbackTransaction: sandbox.stub().returnsThis(),
commitTransaction: sandbox.stub().returnsThis(),
release: sandbox.stub().returnsThis(),
manager: { save: sandbox.stub().returnsThis() },
};
// Simulating TypeORM's Connection
const fakeConnection = {
createQueryRunner: sandbox.stub().returns( fakeQueryRunner ),
getRepository: sandbox.stub().returnsThis(),
findOneOrFail: sandbox.stub().returnsThis(),
}
// I've hidden the mock of my parameters/entities (Usuarios, Senhas, Cidades) since I don't think it's needed to solve the problem.
describe('UserRepository', function () {
let userRepository;
beforeEach(async () => {
const module = await Test.createTestingModule({
providers: [
UserRepository,
],
}).compile();
userRepository = module.get<UserRepository>(UserRepository);
});
describe('signUp', function () {
beforeEach(function () {
// typeORM.getConnection() returns my object simulating a Connection.
sandbox.stub(typeorm, 'getConnection').returns( fakeConnection as unknown as typeorm.Connection )
// Stubbing this.create()
sandbox.stub(userRepository, 'create').returns( Usuarios )
});
afterEach(function () {
sandbox.restore();
});
it('successfully signs up the user', async function () {
// Simulating sucessful save transaction
fakeQueryRunner.manager.save.onCall(0).resolves(Usuarios);
fakeQueryRunner.manager.save.onCall(1).resolves(Senhas);
// Calling my method
await userRepository.signUp(mockCredentialsDto);
// First interation, this line works
sandbox.assert.calledOnceWithExactly(fakeConnection.getRepository, Cidades);
// Asserting that transaction was commited
sandbox.assert.calledOnce(fakeQueryRunner.commitTransaction);
sandbox.assert.notCalled(fakeQueryRunner.rollbackTransaction);
});
it('throws a conflic exception as username already exists', async function () {
// Simulating a reject from transaction
fakeQueryRunner.manager.save.onCall(0).rejects({ code: '23505' });
// Calling my method and catching error
try {
await userRepository.signUp(mockCredentialsDto)
} catch (err) {
expect(err).toEqual(new Error('Email já cadastrado'));
}
// Second interation, this line giver ERROR (method called twice)
sandbox.assert.calledOnceWithExactly(fakeConnection.getRepository, Cidades);
// Asserting that transaction was rolled back, this also gives an error since commitTransaction was called once (in the first 'it' block)
sandbox.assert.notCalled(fakeQueryRunner.commitTransaction);
sandbox.assert.calledOnce(fakeQueryRunner.rollbackTransaction);
});
// I will make another describe block here eventually
});
这是我正在测试的方法:
async signUp(authCredentialsDto: AuthCredentialsDto): Promise<signUpMessage> {
const { nome, email, genero, dataNascimento, profissao, organizacao, atuacao, nomeCidade, uf, senha } = authCredentialsDto;
const connection = getConnection();
const user = this.create();
const senhas = new Senhas;
const cidade = await connection.getRepository(Cidades).findOneOrFail({where: { nome: nomeCidade, uf: uf } });
// Set values
user.nome = nome;
user.email = email;
user.genero = genero;
user.dataNascimento = dataNascimento;
user.profissao = profissao;
user.organizacao = organizacao;
user.atuacao = atuacao;
user.idCidade = cidade;
const salt = await bcrypt.genSalt();
senhas.senha = await this.hashPassword(senha, salt)
senhas.idUsuario2 = user;
// Make a transaction to save the data
const queryRunner = connection.createQueryRunner();
await queryRunner.connect();
await queryRunner.startTransaction();
try {
await queryRunner.manager.save(user);
await queryRunner.manager.save(senhas);
await queryRunner.commitTransaction();
} catch (error) {
if ( error.code === '23505' ) { // Usuário repetido
await queryRunner.rollbackTransaction();
throw new ConflictException('Email já cadastrado')
} else {
await queryRunner.rollbackTransaction();
throw new InternalServerErrorException;
}
} finally {
await queryRunner.release();
}
let success: signUpMessage;
return success;
}
为了让它工作,我在 beforeEach 中声明了我的 Sinon 沙盒和对象,而不是开始我的测试。
我的测试现在看起来像这样:
describe('UserRepository', () => {
let userRepository
let sandbox
let fakeQueryRunner
let fakeConnection
let mockUser: Usuarios
beforeEach(async () => {
sandbox = sinon.createSandbox()
// Cria um objeto QueryRunner fake
fakeQueryRunner = {
connect: sandbox.stub().returnsThis(),
startTransaction: sandbox.stub().returnsThis(),
rollbackTransaction: sandbox.stub().returnsThis(),
commitTransaction: sandbox.stub().returnsThis(),
release: sandbox.stub().returnsThis(),
manager: { save: sandbox.stub().returnsThis() },
}
// Cria um objeto Connection fake (note que o stub de createQueryRunner retorna o objeto fakeQueryRunner )
fakeConnection = {
createQueryRunner: sandbox.stub().returns(fakeQueryRunner),
getRepository: sandbox.stub().returnsThis(),
findOneOrFail: sandbox.stub().returnsThis(),
}
mockUser = {
idUsuario: '1',
atuacao: 'lol',
dataNascimento: '10/10/2021',
email: 'teste@kik.com',
genero: Genero.MASCULINO,
nome: 'Teste Nome',
organizacao: 'Teste org',
profissao: 'Teste Prof',
idCidade: mockCidade,
membros: [],
senhas: mockSenha,
validatePassword: sandbox.stub().returnsThis(),
}
const module = await Test.createTestingModule({
providers: [UserRepository],
}).compile()
userRepository = module.get<UserRepository>(UserRepository)
})
afterEach(() => {
sandbox.restore()
})
describe('signUp', () => {
beforeEach(function () {
// Cria um método falso que retorna o objeto fakeConnection
sandbox.stub(typeorm, 'getConnection').returns((fakeConnection as unknown) as typeorm.Connection)
// Simula o Create de UserRepository
sandbox.stub(userRepository, 'create').returns(Usuarios)
})
it('successfully signs up the user', async () => {
// Salvar Usuário e Senha foi bem sucedido
fakeQueryRunner.manager.save.onCall(0).resolves(Usuarios)
fakeQueryRunner.manager.save.onCall(1).resolves(Senhas)
await userRepository.signUp(mockCredentialsDto)
// Verificando instanciação do repositório
const call1 = fakeConnection.getRepository.onCall(0).resolves(Cidades)
sandbox.assert.calledWith(call1, Cidades)
sandbox.assert.calledOnceWithExactly(fakeConnection.getRepository, Cidades)
// Verificando Transactions
sandbox.assert.calledOnce(fakeQueryRunner.commitTransaction)
sandbox.assert.notCalled(fakeQueryRunner.rollbackTransaction)
})
it('throws a conflic exception as username already exists', async () => {
// Houve um erro em um dos saves, resultando num rollback da transaction
fakeQueryRunner.manager.save.onCall(0).resolves(Usuarios)
fakeQueryRunner.manager.save.onCall(1).rejects({ code: '23505' })
try {
await userRepository.signUp(mockCredentialsDto)
} catch (err) {
expect(err).toEqual(new Error('Email já cadastrado'))
}
// Verificando instanciação do repositório
const call1 = fakeConnection.getRepository.onCall(0).resolves(Cidades)
sandbox.assert.calledWith(call1, Cidades)
sandbox.assert.calledOnceWithExactly(fakeConnection.getRepository, Cidades)
// Verificando Transactions
sandbox.assert.notCalled(fakeQueryRunner.commitTransaction)
sandbox.assert.calledOnce(fakeQueryRunner.rollbackTransaction)
})
})
})