sandbox.restore() 不会重置存根的调用计数

问题描述

我对 Sinon/Jest 和单元测试完全陌生,所以我有点迷失在这里。我尝试创建一个沙箱来声明其中的所有存根,但即使在使用 sandBox.restore() 后,存根的调用计数仍会保留,因此我的测试在下一个“it”中失败。

我无法直接存根 TypeORM 的对象,所以我决定只使用我需要的方法创建假对象,并使 TypeORM 的 getRepository() 使用我创建的对象。

我不确定这种方法是否正确,但看起来我的测试正在运行,我可以断言调用次数及其参数,在第二个“it”中,我可以预期抛出的错误等于“Email já”地籍”,如果我更改了预期的消息,则测试失败。

问题是在下一个“it”块之前调用次数不会重置。所以我总是在“sandBox.assert.CalledOnceWithExactly(fakeConnection.getRepository,Cidades)”行上收到一个错误,因为它被调用了两次(一次是在前一个'it'上,第二次是在当前块上)。

如果我删除该行,我会在断言事务部分收到错误消息,因为我预计 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 Sandbox 和 Objects,而不是在我的测试开始时声明。

我的测试现在看起来像这样:

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)

            // Verificando Transactions
            sandbox.assert.notCalled(fakeQueryRunner.commitTransaction)
            sandbox.assert.calledOnce(fakeQueryRunner.rollbackTransaction)
        })
    })
})

相关问答

Selenium Web驱动程序和Java。元素在(x,y)点处不可单击。其...
Python-如何使用点“。” 访问字典成员?
Java 字符串是不可变的。到底是什么意思?
Java中的“ final”关键字如何工作?(我仍然可以修改对象。...
“loop:”在Java代码中。这是什么,为什么要编译?
java.lang.ClassNotFoundException:sun.jdbc.odbc.JdbcOdbc...