python - Why did my mock test work with just a method instead of a full class? - Stack Overflow

I always thought that you could mock a method on a class only when the class it is coming from is not i

I always thought that you could mock a method on a class only when the class it is coming from is not instantiated in the code under test. And if it is you have to mock the class (just as you have to do for builtins).

But today I made a "mistake" and saw that it just worked. So now I'm confused on when to use mocking a full class and when to use mocking a method on a class.

Let's assume I have the following 3 example files:

Engine.py:

class Engine(object):
    def __init__(self, start_temperature):
        self._started = False
        self._temperature = start_temperature

    def start(self):
        self._started = True
        self._set_temperature(10)

    def stop(self):
        self._started = False
        self._set_temperature(-10)

    def get_temperature(self):
        return self._temperature

    def _set_temperature(self, value):
        self._temperature += value

MyClass.py:

from Engine import Engine

class MyClass(object):
    def __init__(self):
        self.engine = Engine(0)

    def check_start(self):
        self.engine.start()

    def check_stop(self):
        self.engine2 = Engine(1)
        self.engine2.stop()

    def get_temperature(self):
        return self.engine.get_temperature()

and finally the actual test: test_MyClass.py:

import unittest
import unittest.mock as mock

import MyClass

class TestMyClass(unittest.TestCase):
    def test__initiation_returns_0_temperature(self):
        c = MyClass.MyClass()
        temperature = c.get_temperature()
        
        self.assertEqual(temperature, 0)

    @mock.patch("MyClass.Engine")
    def test_engine_get_temperature_mocked_via_class(self, mock_Engine_cls):
        mock_Engine_cls.return_value.get_temperature.return_value = 11
        
        c = MyClass.MyClass()
        c.check_start()
        temperature = c.get_temperature()
        
        self.assertEqual(temperature, 11)

    @mock.patch("MyClass.Engine.get_temperature")
    def test_engine_get_temperature_mocked_via_method(self, get_temperature_mock):
        get_temperature_mock.return_value = 12
        
        c = MyClass.MyClass()
        c.check_stop()
        temperature = c.get_temperature()
        
        self.assertEqual(temperature, 12)
if __name__ == "__main__":
    unittest.main()

I would have expected the 3rd test to error since in the past I got errors for instantiated classes if I did not mock the full class like done in the 2nd test case, but now it passes.

Why is that?

I always thought that you could mock a method on a class only when the class it is coming from is not instantiated in the code under test. And if it is you have to mock the class (just as you have to do for builtins).

But today I made a "mistake" and saw that it just worked. So now I'm confused on when to use mocking a full class and when to use mocking a method on a class.

Let's assume I have the following 3 example files:

Engine.py:

class Engine(object):
    def __init__(self, start_temperature):
        self._started = False
        self._temperature = start_temperature

    def start(self):
        self._started = True
        self._set_temperature(10)

    def stop(self):
        self._started = False
        self._set_temperature(-10)

    def get_temperature(self):
        return self._temperature

    def _set_temperature(self, value):
        self._temperature += value

MyClass.py:

from Engine import Engine

class MyClass(object):
    def __init__(self):
        self.engine = Engine(0)

    def check_start(self):
        self.engine.start()

    def check_stop(self):
        self.engine2 = Engine(1)
        self.engine2.stop()

    def get_temperature(self):
        return self.engine.get_temperature()

and finally the actual test: test_MyClass.py:

import unittest
import unittest.mock as mock

import MyClass

class TestMyClass(unittest.TestCase):
    def test__initiation_returns_0_temperature(self):
        c = MyClass.MyClass()
        temperature = c.get_temperature()
        
        self.assertEqual(temperature, 0)

    @mock.patch("MyClass.Engine")
    def test_engine_get_temperature_mocked_via_class(self, mock_Engine_cls):
        mock_Engine_cls.return_value.get_temperature.return_value = 11
        
        c = MyClass.MyClass()
        c.check_start()
        temperature = c.get_temperature()
        
        self.assertEqual(temperature, 11)

    @mock.patch("MyClass.Engine.get_temperature")
    def test_engine_get_temperature_mocked_via_method(self, get_temperature_mock):
        get_temperature_mock.return_value = 12
        
        c = MyClass.MyClass()
        c.check_stop()
        temperature = c.get_temperature()
        
        self.assertEqual(temperature, 12)
if __name__ == "__main__":
    unittest.main()

I would have expected the 3rd test to error since in the past I got errors for instantiated classes if I did not mock the full class like done in the 2nd test case, but now it passes.

Why is that?

Share Improve this question edited Mar 13 at 13:27 Nemelis asked Mar 3 at 14:54 NemelisNemelis 5,4902 gold badges22 silver badges42 bronze badges 3
  • There is no reason why it shouldn't work. I guess you made an error before when it failed. – MrBean Bremen Commented Mar 3 at 21:34
  • @MrBeanBremen: How can you then in the above example only mock the self.engine2 instance and leave self.engine using the original Engine interface? In real live situations I have had to debug issues where it was wanted to only have 1 instance mocked to be able to check what the SUT was doing it, where the rest was calling the real interface. (Sadly at that time we did not use Python Mock, but some customer made unit test framework which only used fakes) Or are fakes also here the answer?. – Nemelis Commented Mar 5 at 8:17
  • 2 If you want to mock a concrete object (e.g. self.engine2), you have to use mock.patch.object - though that can only mock the object after it is created. – MrBean Bremen Commented Mar 5 at 19:54
Add a comment  | 

1 Answer 1

Reset to default 0

You are naming, one of the files with first letter in lowercase, then you should fix the imports.

As the filename is engine.py you should import from engine import Engine in MyClass.py. I've just changed it, tested your code and it worked flawlessly.

发布者:admin,转转请注明出处:http://www.yc00.com/questions/1745087994a4610520.html

相关推荐

发表回复

评论列表(0条)

  • 暂无评论

联系我们

400-800-8888

在线咨询: QQ交谈

邮件:admin@example.com

工作时间:周一至周五,9:30-18:30,节假日休息

关注微信