web-dev-qa-db-fra.com

Comment se moquer d'une propriété en lecture seule avec une maquette?

Comment se moquer d'une propriété en lecture seule avec mock ?

J'ai essayé:

setattr(obj.__class__, 'property_to_be_mocked', mock.Mock())

mais le problème est qu'il s'applique alors à toutes les instances de la classe ... ce qui casse mes tests.

Avez-vous une autre idée? Je ne veux pas me moquer de l'objet complet, seulement de cette propriété spécifique.

73
charlax

Je pense que la meilleure façon est de se moquer de la propriété en tant que PropertyMock, plutôt que de se moquer de la __get__ méthode directement.

Il est indiqué dans la documentation , recherchez unittest.mock.PropertyMock: Une maquette destinée à être utilisée comme propriété ou autre descripteur sur une classe. PropertyMock fournit __get__ et __set__ afin que vous puissiez spécifier une valeur de retour lors de sa récupération.

Voici comment:

class MyClass:
    @property
    def last_transaction(self):
        # an expensive and complicated DB query here
        pass

def test(unittest.TestCase):
    with mock.patch('MyClass.last_transaction', new_callable=PropertyMock) as mock_last_transaction:
        mock_last_transaction.return_value = Transaction()
        myclass = MyClass()
        print myclass.last_transaction
        mock_last_transaction.assert_called_once_with()
131
jamescastlefield

En fait, la réponse était (comme d'habitude) dans le documentation , c'est juste que j'appliquais le patch à l'instance au lieu de la classe quand j'ai suivi leur exemple.

Voici comment faire:

class MyClass:
    @property
    def last_transaction(self):
        # an expensive and complicated DB query here
        pass

Dans la suite de tests:

def test():
    # Make sure you patch on MyClass, not on a MyClass instance, otherwise
    # you'll get an AttributeError, because mock is using settattr and
    # last_transaction is a readonly property so there's no setter.
    with mock.patch(MyClass, 'last_transaction') as mock_last_transaction:
        mock_last_transaction.__get__ = mock.Mock(return_value=Transaction())
        myclass = MyClass()
        print myclass.last_transaction
39
charlax

Probablement une question de style mais si vous préférez les décorateurs dans les tests, @ jamescastlefield's answer pourrait être changé en quelque chose comme ceci:

class MyClass:
    @property
    def last_transaction(self):
        # an expensive and complicated DB query here
        pass

class Test(unittest.TestCase):
    @mock.patch('MyClass.last_transaction', new_callable=PropertyMock)
    def test(mock_last_transaction):
        mock_last_transaction.return_value = Transaction()
        myclass = MyClass()
        print myclass.last_transaction
        mock_last_transaction.assert_called_once_with()
4
Eyal Levin

Si l'objet dont vous souhaitez remplacer la propriété est un objet factice, vous n'avez pas besoin d'utiliser patch.

Au lieu de cela, peut créer un PropertyMock puis remplacer la propriété sur le type de la maquette. Par exemple, pour remplacer mock_rows.pages propriété à renvoyer (mock_page, mock_page,):

mock_page = mock.create_autospec(reader.ReadRowsPage)
# TODO: set up mock_page.
mock_pages = mock.PropertyMock(return_value=(mock_page, mock_page,))
type(mock_rows).pages = mock_pages
2
Tim Swast

Si vous ne voulez pas tester si la propriété simulée a été accédée ou non, vous pouvez simplement la patcher avec le return_value Attendu.

with mock.patch(MyClass, 'last_transaction', Transaction()):
    ...
0
Simon Charette