lundi 10 juin 2019

Mock @property in injected class - dependency injection

I've been facing an issue while using unitttest.mock and pytest to Mock a dependency class into another one.

I'm testing a class Card that receives an GenericAPI class into its constructor.

The GenericAPI class has the following:

class GenericAPI(CardAPI):

    REQUEST = {
        'API': 'xxx',
        'CARDS_ENDPOINT': 'xxx',
        'DELAY': 0.1,
        'ENCODING': 'utf-8',
        'HEADER_AUTH': {'Authorization': 'Bearer cs{client_secret}'},
        'HEADER_CONTENT': {'Content-Type': 'application/json'},
    }

    def __init__(self, name):
        super().__init__(self)
        self.name = name
        self._value = None

    def get_card(self, name):

        try:
            response = requests.get(url=f'{self.REQUEST["API"]}{self.REQUEST["CARDS_ENDPOINT"]}?exact={name}')
            response.raise_for_status()
            return response.json()
        except requests.HTTPError as err:
            logger.debug(f'Card "{name}" not found. trying fuzzy match. Response: {err}')
            try:
                response = requests.get(url=f'{self.REQUEST["API"]}{self.REQUEST["CARDS_ENDPOINT"]}?fuzzy={name}')
                response.raise_for_status()
                return response.json()
            except requests.HTTPError as err:
                logger.debug(f'Card "{name}" not found. Response: {err}')
                raise ValueError

    @property
    def value(self):
        return self._value

    @value.getter
    def value(self):
        prices = self.get_card(name=self.name)['prices']
        self.value = dict({'foil': prices['usd_foil'], 'non-foil': prices['usd']})
        return self._value

    @value.setter
    def value(self, new_value):
        self._value = new_value

And the Card class that depends on it is defined by

class Card:

    def __new__(cls, *args, **kwargs):

        try:
            kwargs['external_api'].get_card(name=kwargs['name'])
            return super(Card, cls).__new__(cls)
        except ValueError:
            raise ValueError('Unable to instantiate Card with current value as it is not a valid card')

    def __init__(self, name, set_name=None, condition=None, foil=None, external_api=None):

        self.name = name
        self.set_name = set_name
        self.condition = condition
        self.foil = foil
        self._value = None
        self._external_api = external_api

    @property
    def value(self):
        return float(self.value)

    @value.getter
    def value(self):
        card = self._external_api(name=self.name)
        price = card.value

        if self.foil:
            self.value = price['foil']
            return self._value
        else:
            self.value = price['non-foil']
            return self._value

    @value.setter
    def value(self, new_value):
        self._value = new_value

The Test is defined as follows:

@pytest.fixture
def mock_external_api():
    m = MagicMock(spec=GenericAPI)
    m.get_card.return_value = good_response
    type(m).value = PropertyMock(return_value=good_response['prices'])
    print(m.value)
    return m

class TestCard:

    def test_card_price_is_float(self, mock_external_api):
        card = Card(name='mox opal', external_api=mock_external_api)
        assert isinstance(card.value, float)

The error is the following:

=============================================================================================== FAILURES ================================================================================================
___________________________________________________________________________________ TestCard.test_card_price_is_float ____________________________________________________________________________________

self = <test_card.TestCard object at 0x7f3ffdbeffd0>, mock_external_api = <MagicMock spec='ScryfallAPI' id='139912816342632'>

    def test_card_price_is_float(self, mock_external_api):
        card = Card(name='mox opal', external_api=mock_external_api)
>       assert isinstance(card.value, float)
E       AssertionError: assert False
E        +  where False = isinstance(<MagicMock name='mock().value.__getitem__()' id='139912816374616'>, float)
E        +    where <MagicMock name='mock().value.__getitem__()' id='139912816374616'> = (<class 'collection_app.cards.Card'>, {'name': 'mox opal', 'set_name': None, 'condition': None, 'foil': None, '_value'...ck().value.__getitem__()' id='139912816374616'>, '_external_api': <MagicMock spec='GenericAPI' id='139912816342632'>}).value

collection_app/tests/unit/test_card.py:37: AssertionError
----------------------------------------------------------------------------------------- Captured stdout setup ------------------------------------------------------------------------------------------
{'usd': '91.40', 'usd_foil': '111.66', 'eur': '69.87', 'tix': '30.29'}
=================================================================================== 1 failed, 2 passed in 0.16 seconds ===================================================================================

What could be the mistake here? I took my time to read the unittest.mock docs yet I don't seem to grasp what is missing here.

Aucun commentaire:

Enregistrer un commentaire