Mockowanie funkcji w celu wywołania wyjątku w celu przetestowania bloku except

119

Mam funkcję ( foo), która wywołuje inną funkcję ( bar). Jeśli wywołanie bar()wywołuje podniesienie HttpError, chcę to obsłużyć specjalnie, jeśli kod statusu to 404, w przeciwnym razie ponownie podniesie.

Próbuję napisać kilka testów jednostkowych wokół tej foofunkcji, wyszydzając wywołanie bar(). Niestety, nie mogę uzyskać wyśmiewanego wezwania bar()do podniesienia wyjątku, który został przechwycony przez mój exceptblok.

Oto mój kod, który ilustruje mój problem:

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()

I po Mock dokumenty , które mówią, że należy ustawić side_effectz Mockinstancji do Exceptionklasy mają szydzili funkcja podniesienia błąd.

Przyjrzałem się również innym powiązanym pytaniom i odpowiedziom dotyczącym StackOverflow i wygląda na to, że robię to samo, co oni, aby wywołać wyjątek przez ich próbę.

Dlaczego jest ustawienie side_effectod barMocknie powodując oczekiwany Exceptionzostać podniesiona? Jeśli robię coś dziwnego, jak mam przetestować logikę w moim exceptbloku?

Jesse Webb
źródło
Jestem prawie pewien, Twój wyjątek jest podniesione, ale nie jestem pewien, jak jesteś ustawienie resp.statustam kod. Skąd się HTTPErrorbierze?
Martijn Pieters
@MartijnPieters HttpErrorto klasa zdefiniowana w bibliotece Google,apiclient której używamy w GAE. Jest __init__zdefiniowany za pomocą parametrów, (resp, content)więc próbowałem utworzyć próbną instancję dla odpowiedzi, z określonym odpowiednim kodem stanu.
Jesse Webb
Racja, więc to jest ta klasa ; ale nie musisz return_valuewtedy używać użytkownika ; respnie jest wezwany .
Martijn Pieters
1
Ponownie wypróbowałem swój kod bez użycia HttpErrori zamiast tego spróbowałem użyć zwykłej Exceptioninstancji. To działa doskonale. Oznacza to, że musi to mieć coś wspólnego z konfiguracją HttpErrorinstancji, prawdopodobnie związaną z tym, jak tworzę Mockinstancję dla odpowiedzi.
Jesse Webb

Odpowiedzi:

141

Twoja makieta podnosi wyjątek, ale error.resp.statusbrakuje wartości. Zamiast używać return_value, po prostu powiedz, Mockże statusjest to atrybut:

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

Dodatkowe argumenty słów kluczowych do Mock()są ustawiane jako atrybuty obiektu wynikowego.

Umieściłem twoje fooi bardefinicje w my_testsmodule, dodanym na HttpErrorzajęciach, więc mogę go również użyć, a twój test może zostać uruchomiony pomyślnie:

>>> 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

Możesz nawet zobaczyć przebieg print '404 - %s' % error.messagelinii, ale myślę, error.contentże zamiast tego chciałeś użyć tego; w każdym razie to jest HttpError()zestaw atrybutów z drugiego argumentu.

Martijn Pieters
źródło
2
to side_effectjest kluczowa część
Daniel Butler