Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to mock a property inside a class in Python

This is my first file called user.py

from account import Account    
class User:
   def __init__(self, id):
       self.id = id
       self._account = None

   @property
   def account(self):
       if not self._account:
          self._account = Account(self.id)

       return self._account

   @property
   def has_discount(self)
       return self.account.discount_id > 0

I have a second file called account.py

class Account:
    def __init__(self, user_id):
        # some process to load DB data
        self.account = load_account(user_id)
        # do something after this to initialize account properties like discount, etc

    @property
    def discount_id(self):
       return self.discount_id

My goal is to test user.py. One of the things I want to do is to mock the Account object in user.py for the 'has_discount' property decorator. I want to test different scenarios where has_discount will return either 0 or any other number.

How do I do this using patch where I can mock the Account object in the User class to return custom values so I can try different tests?

like image 984
DLS Avatar asked May 04 '16 05:05

DLS


People also ask

How do you mock an object in Python?

If you want to mock an object for the duration of your entire test function, you can use patch() as a function decorator. These functions are now in their own file, separate from their tests. Next, you'll re-create your tests in a file called tests.py .

What is the difference between MagicMock and mock?

So what is the difference between them? MagicMock is a subclass of Mock . It contains all magic methods pre-created and ready to use (e.g. __str__ , __len__ , etc.). Therefore, you should use MagicMock when you need magic methods, and Mock if you don't need them.

Can you mock an import in Python?

Rather than hacking on top of Python's import machinery, you can simply add the mocked module into sys. path , and have Python prefer it over the original module. Now, when the test suite is run, the mocked-lib subdirectory is prepended into sys. path and import A uses B from mocked-lib .


1 Answers

Because the user module imports Account in to its own namespace patching has to be done there instead of the account module. In other words you have to temporarily change what the name Account in the user module refers to:

from user import User
from unittest.mock import patch

with patch('user.Account') as MockAccount:
    MockAccount.return_value.discount_id = 1
    u = User(1)                             
    print(u.has_discount)
    # True

with patch('user.Account') as MockAccount:
    MockAccount.return_value.discount_id = 0
    u = User(1)
    print(u.has_discount)
    # False

I want to test different scenarios where has_discount will return either 0 or any other number.

In its current implementation User.has_discount will always return either True or False. Did you mean Account.discount_id?

like image 71
Ilja Everilä Avatar answered Sep 28 '22 18:09

Ilja Everilä