I know the title is quite confusing, but for the sake of clarity, I created a sample of what I have to transpose:
Let's say I have this code in class_file.py
class CarDealership:
def allow_car_out_of_the_dealership(self, car):
logger.info(f"Driving out of the dealership")
drivers_license = car.driver.wallet.get_drivers_license()
if drivers_license:
try:
allow_car_removal(car, drivers_license)
except:
deny_car_removal(car, drivers_license)
I'm trying to test a situation where the method deny_car_removal
is called. get_valid_license()
is a method specific to driver.wallet
and is not otherwise imported or referenced in class_file.py
in any way, shape or form.
The problem is that the code goes a completely different way if get_valid_license
does not return anything valid in the real situation. I'm trying to patch the drivers_license
so I can eventually bring it to the point I need.
I'm guessing test_class_file.py
should look something like this:
class CarDealershipTests(BaseTestCase):
@patch('class_file.CarDealership.deny_car_removal')
def test_deny_car_removal(self):
# something here
self.assertTrue(mock_deny_car_removal.called)
A few things I have tried from looking at other StackOverflow answers but didn't work:
driver
instance inside the mocked car
, like so: def setUp(self) -> None:
self._create_car
def _create_car(self):
car = Car()
car.driver = MagicMock()
@patch.object(class_file.CarDealership.allow_car_out_of_the_dealership, "car.driver.wallet.get_valid_license")
@patch.object(class_file.CarDealership.allow_car_out_of_the_dealership, "drivers_license")
@patch("class_file.CarDealership.allow_car_out_of_the_dealership.car.driver.wallet.get_valid_license")
@patch("class_file.CarDealership.allow_car_out_of_the_dealership.drivers_license")
Suppose you have a file with the following contents as you have shown:
import logging
logger = logging.getLogger(__name__)
class CarRemovalFailure(BaseException):
pass
class CarDealership:
def allow_car_out_of_the_dealership(self, car):
logger.info("Driving out of the dealership")
drivers_license = car.driver.wallet.get_drivers_license()
if drivers_license:
try:
allow_car_removal(car, drivers_license)
except CarRemovalFailure:
deny_car_removal(car, drivers_license)
In order to have deny_car_removal
be called, we will need allow_car_removal
to throw an Exception
. This is easily done by using the side_effect
attribute of the Mock
object.
I didn't incorporate any setup/tear-down up in this test but instead am illustrating how side_effect
works and how it ends up having deny_car_removal
be called.
from unittest.mock import MagicMock, patch
from class_file import CarDealership, CarRemovalFailure
@patch("class_file.allow_car_removal")
@patch("class_file.deny_car_removal")
def test_deny_car(mock_deny, mock_allow):
mock_allow.side_effect = CarRemovalFailure("Failed!")
car = MagicMock()
cd = CarDealership()
cd.allow_car_out_of_the_dealership(car)
car.driver.wallet.get_drivers_license.assert_called_once()
mock_deny.assert_called_once()
When run it outputs the following:
============================= test session starts ==============================
platform darwin -- Python 3.9.1, pytest-6.2.2, py-1.10.0, pluggy-0.13.1
rootdir: ****
collected 1 item
tests/test_car.py . [100%]
============================== 1 passed in 0.05s ===============================
In your example you might not want to patch deny_car_removal
as you are trying to test the logic, but since I don't know what that might be I patched it on my end. The point of that was to illustrate that in fact the function does get called.