Reputation: 451
The classes are defined as follows-
class User:
def __init__(self):
self.__client = ManagerClient()
self.__data = None
def get_data(self):
if self.__data is None:
self.__get_password()
return self.__data
def __get_password(self):
self.__password = self.__client.fetch_secret()
class ManagerClient:
def fetch_secret():
pass
I want to unit test get_data(), since the constructor of User class is initializing the object of manager client and that object is used later to call fetch_secret(), how should I mock?
Upvotes: 1
Views: 3721
Reputation: 14226
Technically when testing the get_data
method for a unit-test we are not concerned about __get_password
or its implementation. We simply want to test the various logical arms of get_data
. Following that line of thinking we would simply mock __get_password
and its action. If we wanted to test __get_password
we would use a separate test for this.
Another thing to note is because you use double underscores for your attributes and methods they become textually replaced with _classname__attribute
. That is why we patch and mock using this verbiage as shown below. You can see more about that here.
Also as a side note your get_data
function is not fundamentally sound. It has two different return types based on its logic, it can either return something or set an attribute on the object, that is considered bad practice.
Given the following project structure:
stackoverflow/
├── mypackage
│ ├── __init__.py
│ └── users.py
└── tests
├── __init__.py
└── test_users.py
The tests we would write are shown below.
from unittest.mock import MagicMock, patch
from mypackage.users import User
@patch("mypackage.users.ManagerClient")
def test_get_data_valid(mock_client):
expected = "fizz"
klass = User()
klass._User__data = expected
actual = klass.get_data()
assert actual == expected
@patch("mypackage.users.ManagerClient")
@patch.object(User, "_User__get_password")
def test_get_data_none(mock_pwd, mock_client):
klass = User()
klass.get_data()
mock_pwd.assert_called_once()
@patch("mypackage.users.ManagerClient")
def test_get_password(mock_client):
mock_client.configure_mock(
**{
"fetch_secret.return_value": "buzz",
"return_value": mock_client
}
)
klass = User()
klass._User__get_password()
assert klass._User__password == "buzz"
======================================= test session starts ========================================
platform darwin -- Python 3.9.1, pytest-6.2.2, py-1.10.0, pluggy-0.13.1
rootdir: ***/stackoverflow
collected 3 items
tests/test_users.py ... [100%]
======================================== 3 passed in 0.06s =========================================
As requested we mock ManagerClient
in every test so that it does not actually get initialized. This is achieved by patching the relevant class and methods. We also test the __get_password
method by setting a return value for fetch_secret
that we can test against. The reason why we make the mock_client
return itself in the final test is when you you initialize a Mock
object, it returns a new one. To keep things simple we just set the return value of the Mock
to itself that way we don't have to create a new object.
Upvotes: 3