Se moquer d'une fonction pour lever une exception pour tester un bloc except

119

J'ai une fonction ( foo) qui appelle une autre fonction ( bar). Si l'invocation bar()déclenche un HttpError, je veux le gérer spécialement si le code d'état est 404, sinon re-relancer.

J'essaie d'écrire des tests unitaires autour de cette foofonction, en se moquant de l'appel à bar(). Malheureusement, je ne peux pas obtenir l'appel simulé à bar()pour lever une exception qui est interceptée par mon exceptbloc.

Voici mon code qui illustre mon problème:

import unittest
import mock
from apiclient.errors import HttpError


class FooTests(unittest.TestCase):
    @mock.patch('my_tests.bar')
    def test_foo_shouldReturnResultOfBar_whenBarSucceeds(self, barMock):
        barMock.return_value = True
        result = foo()
        self.assertTrue(result)  # passes

    @mock.patch('my_tests.bar')
    def test_foo_shouldReturnNone_whenBarRaiseHttpError404(self, barMock):
        barMock.side_effect = HttpError(mock.Mock(return_value={'status': 404}), 'not found')
        result = foo()
        self.assertIsNone(result)  # fails, test raises HttpError

    @mock.patch('my_tests.bar')
    def test_foo_shouldRaiseHttpError_whenBarRaiseHttpErrorNot404(self, barMock):
        barMock.side_effect = HttpError(mock.Mock(return_value={'status': 500}), 'error')
        with self.assertRaises(HttpError):  # passes
            foo()

def foo():
    try:
        result = bar()
        return result
    except HttpError as error:
        if error.resp.status == 404:
            print '404 - %s' % error.message
            return None
        raise

def bar():
    raise NotImplementedError()

J'ai suivi les documents Mock qui disent que vous devez définir le side_effectd'une Mockinstance sur une Exceptionclasse pour que la fonction simulée lève l'erreur.

J'ai également regardé d'autres questions et réponses liées à StackOverflow, et il semble que je fais la même chose qu'ils font pour provoquer et déclencher une exception par leur simulation.

Pourquoi le réglage side_effectde barMockne provoque-t-il pas le relèvement de l'attendu Exception? Si je fais quelque chose de bizarre, comment dois-je procéder pour tester la logique de mon exceptbloc?

Jesse Webb
la source
Je suis presque sûr que votre exception est soulevée, mais je ne sais pas comment vous définissez le resp.statuscode ici. D'où HTTPErrorvient-il?
Martijn Pieters
@MartijnPieters HttpErrorest une classe définie dans Google apiclientlib que nous utilisons dans GAE. Il __init__est défini avec les paramètres, (resp, content)donc j'essayais de créer une instance fictive pour la réponse, avec le code d'état approprié spécifié.
Jesse Webb
Bon, c'est donc cette classe ; mais vous n'avez pas besoin d'utiliser return_valuealors; respn'est pas appelé .
Martijn Pieters
1
J'ai réessayé mon code sans utiliser HttpErroret j'ai essayé d'utiliser à la place une Exceptioninstance régulière . Cela fonctionne parfaitement. Cela signifie qu'il doit avoir quelque chose à voir avec la façon dont je configure l' HttpErrorinstance, probablement lié à la façon dont je crée une Mockinstance pour la réponse.
Jesse Webb

Réponses:

141

Votre simulation soulève très bien l'exception, mais la error.resp.statusvaleur est manquante. Plutôt que d'utiliser return_value, dites simplement Mockque statusc'est un attribut:

barMock.side_effect = HttpError(mock.Mock(status=404), 'not found')

Les arguments de mot-clé supplémentaires à Mock()sont définis comme attributs sur l'objet résultant.

J'ai mis vos définitions fooet bardans un my_testsmodule, ajouté dans la HttpErrorclasse pour que je puisse l'utiliser aussi, et votre test peut ensuite être exécuté avec succès:

>>> from my_tests import foo, HttpError
>>> import mock
>>> with mock.patch('my_tests.bar') as barMock:
...     barMock.side_effect = HttpError(mock.Mock(status=404), 'not found')
...     result = my_test.foo()
... 
404 - 
>>> result is None
True

Vous pouvez même voir la print '404 - %s' % error.messageligne courir, mais je pense que vous vouliez l'utiliser à la error.contentplace; c'est en tout cas les HttpError()ensembles d' attributs du deuxième argument.

Martijn Pieters
la source
2
la side_effectest la partie clé
Daniel Butler