user8082934
user8082934

Reputation: 451

How to mock the constructor pytest

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

Answers (1)

gold_cy
gold_cy

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

Related Questions