Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

unittest.mock.patch a class instance, and set method return values

I've got an Alarm object that uses a Sensor object. In my test I'd like to patch Sensor with a Stub. The code below works, but I have to explicitly pass in the stub to the Alarm constructor:

#tire_pressure_monitoring.py
from sensor import Sensor

class Alarm:

    def __init__(self, sensor=None):
        self._low_pressure_threshold = 17
        self._high_pressure_threshold = 21
        self._sensor = sensor or Sensor()
        self._is_alarm_on = False

    def check(self):
        psi_pressure_value = self._sensor.sample_pressure()
        if psi_pressure_value < self._low_pressure_threshold or self._high_pressure_threshold < psi_pressure_value:
            self._is_alarm_on = True

    @property
    def is_alarm_on(self):
        return self._is_alarm_on

#test_tire_pressure_monitoring.py
import unittest
from unittest.mock import patch, MagicMock, Mock

from tire_pressure_monitoring import Alarm
from sensor import Sensor

class AlarmTest(unittest.TestCase):

    def test_check_with_too_high_pressure(self):
        with patch('tire_pressure_monitoring.Sensor') as test_sensor_class:
            test_sensor_class.instance.sample_pressure.return_value=22
            alarm = Alarm(sensor=test_sensor_class.instance)
            alarm.check()
            self.assertTrue(alarm.is_alarm_on)

What I'd like to do, but can't seem to find a way to achieve, is to replace the Sensor instance with a stub, without passing anthing to the Alarm constructor. This code looks to me like it should work, but doesn't:

    def test_check_with_too_high_pressure(self):
    with patch('tire_pressure_monitoring.Sensor') as test_sensor_class:
        test_sensor_class.instance.sample_pressure.return_value=22
        alarm = Alarm()
        alarm.check()
        self.assertTrue(alarm.is_alarm_on)

The Alarm instance gets an instance of MagicMock, but the 'sample_pressure' method doesn't return 22. Basically I want to know if there is a way to use unittest.mock to test the Alarm class without needing a constructor that takes a Sensor instance as argument.

like image 455
Emily Bache Avatar asked Dec 21 '22 00:12

Emily Bache


1 Answers

When you call test_sensor_class.instance you are using test_sensor_class as a property holder, adding a Mock property instance to which you add a Mock property sample_pressure. Your patch is not used at all, your code is in fact equivalent to:

def test_check_with_too_high_pressure(self):
    instance = MagicMock()
    instance.sample_pressure.return_value=22
    alarm = Alarm(sensor=instance)
    alarm.check()
    self.assertTrue(alarm.is_alarm_on)

What you want to do it patch the call to Sensor().

Using your code, you simply need to set return value of the mocked class test_sensor_class to a preset mock of Sensor.

def test_check_with_too_high_pressure(self):
    with patch('tire_pressure_monitoring.Sensor') as test_sensor_class:
        mockSensor = MagicMock()
        mockSensor.sample_pressure.return_value = 22
        test_sensor_class.return_value = mockSensor
        alarm = Alarm()
        alarm.check()
        self.assertTrue(alarm.is_alarm_on)
like image 65
Rod Avatar answered Dec 31 '22 14:12

Rod