Sinon: Teste com falha inesperada após o lançamento da v7.2.0

Criado em 23 jul. 2019  ·  11Comentários  ·  Fonte: sinonjs/sinon

Descreva o bug
Atualizar o Sinon para a versão mais recente v7 causa algumas falhas inesperadas. A única mudança relevante que consegui identificar foi esta PR: # 1955 - Substituindo deep-equal.js por samsam.deepEqual .
Os testes passam em todas as versões até 7.2.0, revertendo para 7.1.1 (uma versão anterior) não causa nenhum problema e todos os testes passam.

Erro observado:

AssertionError: expected addTracks to have been called with arguments [{ language: "en", trackContentType: "CEA608" }]
[[functionStub] { language: "en", trackContentType: "CEA608" }] [{ language: "en", trackContentType: "CEA608" }]

Erro de produção de teste:

    it('should call getTextTracksManager, Track, and setCaptionLang', () => {
      sinonSandbox.stub(chromecastReceiver, 'setCaptionLang');
      chromecastReceiver.player.getTextTracksManager = sinonSandbox.stub()
        .returns({ addTracks: sinonSandbox.spy() });
      sinonSandbox.stub(cast.framework.messages, 'Track').returns(() => ({}));

      chromecastReceiver.loadCaptions('off');

      expect(chromecastReceiver.player.getTextTracksManager).to.have.callCount(1);
      expect(cast.framework.messages.Track).to.have.been
        .calledWith(3, cast.framework.messages.TrackType.TEXT);
      expect(chromecastReceiver.textTracksManager.addTracks).to.have.been.calledWith([{
        trackContentType: cast.framework.messages.CaptionMimeType.CEA608,
        language: 'en',
      }]);
      expect(chromecastReceiver.setCaptionLang).to.have.been.calledWith('off');
    });

Reproduzir
Passos para reproduzir o comportamento:

  1. Use qualquer versão do Sinon acima de 7.1.1
  2. Execute o teste de amostra (acima)
  3. Ver erro

Comportamento esperado
O teste deve passar

Capturas de tela
Se aplicável, adicione capturas de tela para ajudar a explicar seu problema.

Contexto (preencha as seguintes informações):

  • Versão da biblioteca: 7.2.0+
  • Ambiente: macOS 10.14.5

Avise-me se puder fornecer mais informações sobre isso.

Bug Regression pinned

Comentários muito úteis

Olá @ fatso83 ,

Obrigado por dedicar seu tempo para analisá-lo. Fiz o que você me pediu e criei uma demonstração do runkit que mostra o bug. Basta comentar / descomentar as diferentes instruções require('sinon') e executar o código para ver o bug.

https://runkit.com/danielkg/sinon-issue-reproducible-bug-template

Eu posto o código aqui também, caso o runkit não funcione por qualquer motivo.

// Employs 'mini-mocha' to emulate running in the Mocha test runner (mochajs.org)
require("@fatso83/mini-mocha").install();
// const sinon = require('[email protected]'); // WORKS!
const sinon = require('[email protected]'); // FAILS!
// const sinon = require('sinon');       // FAILS!
const { assert } = require('@sinonjs/referee');

const SAMPLE_USER = {
    id: 1,
    name: 'Kid',
    age: 22,
    weight: 83.5,
    notes: [{ txt: 'abc' }, { txt: 'def' }, { txt: 'ghi' }],
};

class MyUser {
    constructor(id, name, age, weight, notes = []) {
        this.id = id;
        this.name = name;
        this.age = age;
        this.weight = weight;
        this.notes = notes;
    }
}

const createFakeUser = overrides => ({
    id: '007',
    name: 'Bond',
    ...overrides,
});

class Talkie {
    talk(user, topic) {
        console.log('talked to', user.name, 'about', topic);
    }
}

const action = (userId, talkie) => {
    const { name, age, weight, notes } = SAMPLE_USER;
    const user = new MyUser(userId, name, age, weight, notes);

    talkie.talk(user, 'apples');

    return user;
}


describe('stubbing', () => {
    const talkie = new Talkie();

    afterEach(() => {
        sinon.restore();
    });

    beforeEach(() => {
        sinon.spy(talkie, 'talk');
    });

    it('is the same user', () => {
        const userA = action(1, talkie);
        const userB = createFakeUser(SAMPLE_USER);

        assert.equals(JSON.stringify(userA), JSON.stringify(userB));
    });

    it('should set the return value', () => {
        const userA = action(1, talkie);
        const userB = createFakeUser(SAMPLE_USER);

        sinon.assert.calledWith(talkie.talk, userB, sinon.match.string);
    });
});

Por favor, dê uma outra olhada quando tiver tempo. Obrigada.

Atenciosamente,
Daniel

Todos 11 comentários

Obrigado por um ótimo relatório de bug em geral. Porém, ele tem um grande déficit: não consigo executar seu exemplo para verificação ou rastreamento do bug! Não é executável por conta própria e, sem uma forma de reprodução, é difícil consertar.

Você poderia fazer um candidato mínimo para reprodução? Você não precisa replicar todo o exemplo original, mais como uma simplificação como var myObject = { aMethod: function(){}, aProp: 42 }; // etc .

Se soubéssemos que isso estava quebrando, é claro que teríamos aumentado a versão principal, então um teste de regressão seria ótimo!

PS Parece que sinon-chai também está envolvido. Isso vale a pena listar.

@ fatso83 Deixe-me ver como posso obter um exemplo isolado e executável para reprodução

Alguma atualização?

Este problema foi marcado automaticamente como obsoleto porque não teve atividades recentes. Ele será fechado se nenhuma outra atividade ocorrer. Obrigado por suas contribuições.

@mantoni Você não relatou ter experimentado algo assim em um de seus projetos?

Desculpe pela falta de atualizações, não estou mais associado ao projeto que continha o problema. Se outra pessoa pode estar enfrentando o mesmo problema, seria ótimo se ela pudesse fornecer um exemplo para reprodução

@ fatso83 Não me lembro de ter encontrado o mesmo problema. Eu atualizei para o Sinon mais recente em alguns projetos sem problemas.

Fechando como não pode reproduzir

Desculpe por reviver este problema dos mortos, mas estou enfrentando o mesmo problema.

_O código abaixo é simplificado para fins ilustrativos._

Em meu aplicativo, tenho esta aula

class MyUser {
  // ... some user properties  
}

e eu o uso mais tarde em uma função de controlador Koa como esta

const mqClient = require('./mqClient');

// GET /userDetails/<id> endpoint
async function getUserDetailsController(req, res) {
  const user = new MyUser();

  // populate user with properties etc.
  user.id = '1';
  user.name = 'Hans Gruber';
  user.tasks = [{ id: '42', title: 'execute Nakatomi tower heist' }];

  // ... get more details ...

  // Notify other users about this user
  mqClient.notifyOthers(user);

  res.json({ ok: true, user });
}

E no meu getUserDetailsController.test.js eu faço algo assim:

const mqClient = require('./mqClient');

const createExpectedUser = overrides => {
  id: '1',
  name: 'Hans Gruber',
  tasks: [{ id: '42', title: 'execute Nakatomi tower heist' }],
  ...overrides
};

it('does not fail', async () => {
  sinon.spy(mqClient, 'notifyOthers');

  const expectedUser = createExpectedUser();

  await presetRequest // helper object to run the controller code
    .get('/userDetails/1')
    .expect(200);

  // This works with sinon 7.1.1.
  // This fails with sinon 7.2.0 and onwards.
  sinon.assert.calledWithMatch(mqClient.notifyOthers, expectedUser);
});

Eu testei isso com o sinon 7.1.1 (que usa "2.1.3" de samsam ) e funcionou.
A versão 7.2.0 falha (usa 3.3.3 de samsam ).
A versão 9.2.3 mais recente também falha (usa 5.3.0 de samsam ).

Estou no Nó 14.5.3, mas isso também acontece no Nó 12.20.0.

Eu comparei user no controlador e expectedUser no teste via JSON.stringify manualmente e eles são 100% iguais. A única diferença sintaticamente é que o controlador usa a classe, enquanto o teste usa um literal de objeto.

Alguém tem uma boa ideia do porquê isso acontece? Ou alguém tem uma boa idéia de como contornar isso?

Atenciosamente,
Daniel

Enquanto não tivermos um caso reproduzível para verificar isso, não podemos justificar o gasto de nossas horas livres investigando isso, infelizmente. Eu não teria nada para procurar e nada para verificar se estava certo.

Eu adoro consertar bugs, então se você puder gastar algum tempo tentando fazer algo _Eu posso executar_, faça-o! RunKit é um ótimo serviço para fazer isso: https://runkit.com/fatso83/sinon-issue-reproducible-bug-template

Olá @ fatso83 ,

Obrigado por dedicar seu tempo para analisá-lo. Fiz o que você me pediu e criei uma demonstração do runkit que mostra o bug. Basta comentar / descomentar as diferentes instruções require('sinon') e executar o código para ver o bug.

https://runkit.com/danielkg/sinon-issue-reproducible-bug-template

Eu posto o código aqui também, caso o runkit não funcione por qualquer motivo.

// Employs 'mini-mocha' to emulate running in the Mocha test runner (mochajs.org)
require("@fatso83/mini-mocha").install();
// const sinon = require('[email protected]'); // WORKS!
const sinon = require('[email protected]'); // FAILS!
// const sinon = require('sinon');       // FAILS!
const { assert } = require('@sinonjs/referee');

const SAMPLE_USER = {
    id: 1,
    name: 'Kid',
    age: 22,
    weight: 83.5,
    notes: [{ txt: 'abc' }, { txt: 'def' }, { txt: 'ghi' }],
};

class MyUser {
    constructor(id, name, age, weight, notes = []) {
        this.id = id;
        this.name = name;
        this.age = age;
        this.weight = weight;
        this.notes = notes;
    }
}

const createFakeUser = overrides => ({
    id: '007',
    name: 'Bond',
    ...overrides,
});

class Talkie {
    talk(user, topic) {
        console.log('talked to', user.name, 'about', topic);
    }
}

const action = (userId, talkie) => {
    const { name, age, weight, notes } = SAMPLE_USER;
    const user = new MyUser(userId, name, age, weight, notes);

    talkie.talk(user, 'apples');

    return user;
}


describe('stubbing', () => {
    const talkie = new Talkie();

    afterEach(() => {
        sinon.restore();
    });

    beforeEach(() => {
        sinon.spy(talkie, 'talk');
    });

    it('is the same user', () => {
        const userA = action(1, talkie);
        const userB = createFakeUser(SAMPLE_USER);

        assert.equals(JSON.stringify(userA), JSON.stringify(userB));
    });

    it('should set the return value', () => {
        const userA = action(1, talkie);
        const userB = createFakeUser(SAMPLE_USER);

        sinon.assert.calledWith(talkie.talk, userB, sinon.match.string);
    });
});

Por favor, dê uma outra olhada quando tiver tempo. Obrigada.

Atenciosamente,
Daniel

Esta página foi útil?
0 / 5 - 0 avaliações