Sinon: Test échouant de manière inattendue après la version v7.2.0

Créé le 23 juil. 2019  ·  11Commentaires  ·  Source: sinonjs/sinon

Décrivez le bogue
La mise à niveau d'Autre vers la dernière version v7 entraîne des échecs inattendus. Le seul changement pertinent que j'ai pu identifier était ce PR: # 1955 - Remplacement de deep-equal.js par samsam.deepEqual .
Les tests réussissent toutes les versions jusqu'à 7.2.0, le retour à 7.1.1 (une version précédente) ne pose aucun problème et tous les tests réussissent.

Erreur observée:

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

Test produisant l'erreur:

    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');
    });

Reproduire
Étapes pour reproduire le comportement:

  1. Utilisez n'importe quelle version Sinon au-dessus de 7.1.1
  2. Exécuter un test d'échantillon (ci-dessus)
  3. Voir l'erreur

Comportement prévisible
Le test doit réussir

Captures d'écran
Le cas échéant, ajoutez des captures d'écran pour expliquer votre problème.

Contexte (veuillez compléter les informations suivantes):

  • Version de la bibliothèque: 7.2.0+
  • Environnement: macOS 10.14.5

Veuillez me faire savoir si je peux fournir plus d'informations à ce sujet.

Bug Regression pinned

Commentaire le plus utile

Salut @ fatso83 ,

Merci d'avoir pris le temps de l'examiner. J'ai fait ce que vous m'avez demandé et j'ai créé une démo de runkit qui montre le bogue. Il suffit de commenter / décommenter les différentes instructions require('sinon') et d'exécuter le code pour voir le bogue.

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

Je poste le code ici aussi au cas où runkit ne fonctionnerait pas pour une raison quelconque.

// 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);
    });
});

Veuillez jeter un autre coup d'œil lorsque vous en aurez le temps. Merci.

Bien à vous,
Daniel

Tous les 11 commentaires

Merci pour un rapport de bogue généralement excellent. Il a cependant un déficit majeur: je ne peux pas exécuter votre exemple pour vérification ou pour traquer le bogue! Il n'est pas exécutable seul, et sans moyen de reproduction, il est difficile de résoudre ce problème.

Pourriez-vous faire un candidat minimal pour la reproduction? Vous n'êtes pas obligé de reproduire l'intégralité de l'exemple d'origine, plutôt comme une simplification comme var myObject = { aMethod: function(){}, aProp: 42 }; // etc .

Si nous avions su que cela cassait, nous aurions bien sûr augmenté la version majeure, donc un test de régression serait génial!

PS On dirait que sinon-chai est également impliqué. Cela vaut la peine d'être répertorié.

@ fatso83 Voyons comment je peux obtenir un exemple isolé et exécutable pour la reproduction

Les mises à jour?

Ce problème a été automatiquement marqué comme obsolète car il n'a pas eu d'activité récente. Il sera fermé si aucune autre activité ne se produit. Merci pour vos contributions.

@mantoni N'avez-vous pas signalé avoir vécu quelque chose comme ça dans l'un de vos projets?

Désolé pour le manque de mises à jour, je ne suis plus associé au projet qui contenait le problème. Si quelqu'un d'autre peut rencontrer le même problème, ce serait formidable s'il pouvait fournir un exemple pour la repro

@ fatso83 Je ne me souviens pas avoir rencontré le même problème. J'ai mis à niveau vers le dernier Sinon dans quelques projets sans aucun problème.

Fermer comme ne peut pas se reproduire

Désolé d'avoir ressuscité ce problème d'entre les morts, mais je rencontre le même problème.

_Le code ci-dessous est simplifié à des fins d'illustration._

Dans mon application, j'ai cette classe

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

et je l'utilise plus tard dans une fonction de contrôleur Koa comme celle-ci

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 });
}

Et dans mon getUserDetailsController.test.js je fais quelque chose comme ceci:

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);
});

J'ai testé cela avec sinon 7.1.1 (qui utilise "2.1.3" de samsam ), et cela fonctionne.
La version 7.2.0 échoue (utilise 3.3.3 de samsam ).
La version la plus récente 9.2.3 échoue également (utilise 5.3.0 de samsam ).

Je suis sur le nœud 14.5.3, mais cela se produit également sur le nœud 12.20.0.

J'ai comparé le user dans le contrôleur et le expectedUser dans le test via JSON.stringify manuellement et ils sont égaux à 100%. La seule différence syntaxique est que le contrôleur utilise la classe, tandis que le test utilise un littéral objet.

Quelqu'un a-t-il une bonne idée de pourquoi cela se produit? Ou quelqu'un a-t-il une bonne idée sur la façon de contourner ce problème?

Bien à vous,
Daniel

Tant que nous n'avons pas de cas reproductible pour vérifier cela, nous ne pouvons pas justifier de passer nos heures libres à étudier cela, j'en ai peur. Je n'aurais rien à chercher et rien à vérifier que j'avais raison.

J'adore corriger les bugs, donc si vous pouvez passer un peu de temps à essayer de faire quelque chose que je peux exécuter, faites-le! RunKit est un excellent service pour ce faire: https://runkit.com/fatso83/sinon-issue-reproducible-bug-template

Salut @ fatso83 ,

Merci d'avoir pris le temps de l'examiner. J'ai fait ce que vous m'avez demandé et j'ai créé une démo de runkit qui montre le bogue. Il suffit de commenter / décommenter les différentes instructions require('sinon') et d'exécuter le code pour voir le bogue.

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

Je poste le code ici aussi au cas où runkit ne fonctionnerait pas pour une raison quelconque.

// 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);
    });
});

Veuillez jeter un autre coup d'œil lorsque vous en aurez le temps. Merci.

Bien à vous,
Daniel

Cette page vous a été utile?
0 / 5 - 0 notes