Tests en JavaScriptSpy, Mock et Stubs avec Sinon

Télécharger la vidéo

Découverte de Mocha
Tester sur le navigateur
Tester avec un Framework
Tests fonctionnels
Jest
Jest
39 min

Nos objets et nos librairies JavaScript ne seront pas aussi simples que les librairies que l'on a vues jusqu'à maintenant. Il y a souvent des appels à des sous-méthodes qui font appel à des API tiers et des logiques assez complexes. Dans certains de nos tests, on va vouloir tester qu'une fonction de notre objet est belle et bien appelée, et parfois modifier le retour de nos fonctions pour éviter des appels au serveur. C'est là que la librairie Sinon intervient.

Sinon est une librairie qui va vous permettre de créer des Spy, des Stubs et des Mocks.

Spy

Les Spy, comme son nom vous l'indique, vous permettent d'espionner un objet (plus précisément une méthode de l'objet) et de vérifier qu'elle a bien été appelée avec les bons arguments.

var sinon = require('sinon')

    it('should call callAPI', function(){
      sinon.spy(Social, 'callAPI')
      Social.getTwitterCount(url)
      expect(Social.callAPI.withArgs(Social.twitter_url + url).calledOnce).to.be.true
      Social.callAPI.restore()
    })

Lorsque l'on souhaitera espionner une méthode, on utilisera sinon.spy(). Cette fonction permet de transformer notre objet est d'accéder à des propriétés supplémentaires, par exemple calledOnce permet de vérifier que la méthode n'a été appelée qu'une seule fois. Mais aussi la méthode withArgs() qui permet de vérifier que les arguments passés à cette fonction sont bien les arguments attendus.

Si vous partagez un objet à travers différents tests, il faudra alors penser à utiliser la méthode restore() afin de retirer l'espion de notre méthode pour ne pas affecter nos prochains tests.

Il est évidemment possible de faire d'autres vérifications, dans ce cas-là il faudra vous rendre sur l'API de la fonction spy.

Stubs

Les Stubs quant à eux permettent de modifier une fonction pour pouvoir remplacer son comportement. Par exemple dans un objet complexe, on peut être amené à appeler un serveur pour par exemple récupérer un JSON. Le problème c'est que lors de nos tests on ne souhaite pas que nostests JavaScript dépendent de l'implémentation serveur. Dans ce cas-là on va modifier la fonction chargée d'appeler le serveur et emballe l'obliger à retourner un résultat précis.

it('should return count', function(){
  var stub = sinon.stub(Social, 'callAPI')
  stub.returns({count: 3})
  expect(Social.getTwitterCount(url)).to.eventually.be.equal(3)
  Social.callAPI.restore()
})

Dans ce cas , on modifie la fonction callAPI pour lui faire retourner un objet qui contient la propriété count. Plus d'informations sur les stubs dans la documentation.

Mocks

Les Mocks sont de fausses méthodes (comme les espions) avec un comportement prédéfini (comme les stubs) et avec des attentes préprogrammées. Si vous n'avez pas besoin d'un de ce comportement, vous ne devez absolument pas utiliser les Mocks sous peine de voir votre test échoué.


it('should return shares', function(){
  var mock = sinon.mock(Social)
  mock.expects('callAPI')
    .once()
    .withArgs(Social.facebook_url + url)
    .returns({shares: 10})
  expect(Social.getFacebookCount(url)).to.be.equal(10)
  mock.verify()
  mock.restore()
})

Le Mock intègre des attentes (n'être appelé qu'une seule fois, avec certains arguments) et modifie le comportement de la fonction (retourne {share: 10}

Sinon as promised

Dans beaucoup de cas, nos librairies vont retourner des promesses. Le problème lorsque l'on va souhaiter modifier le retour de nos fonctions (en utilisant des mocks ou des stubs) c'est que l'on va devoir passer initialiser et passer une promesse, ce qui est loin d'être pratique. Heureusement sinon-as-promised est là pour nous aider. On va donc commencer par l'importer dans notre projet

npm i sinon-as-promised --save-dev

et ensuite lorsque l'on va l'inclure dans nos tests il va automatiquement modifier l'objet sinon pour proposer de nouvelles fonctionnalités. On aura par exemple accès à la méthode resolves() qui permet de dire : "La fonction va renvoyer une promesse qui se résoudra avec le retour suivant"

 require('sinon-as-promised')


it('should return shares', function(done){
  var mock = sinon.mock(Social)
  mock.expects('callAPI')
    .once()
    .withArgs(Social.facebook_url + url)
    .resolves({shares: 10})
  expect(Social.getFacebookCount(url)).to.eventually.equal(10).notify(done)
  mock.verify()
  mock.restore()
})