I'm trying to understand the different ways to patch a constant in Python using mock.patch. My goal is to be able to use a variable defined in my Test class as the patching value for my constant.
I've found this question which explains how to patch a constant: How to patch a constant in python And this question which explains how to use self in patch: using self in python @patch decorator
But from this 2nd link, I cannot get the testTwo way (providing the mock as a function parameter) to work
Here is my simplified use case:
mymodule.py
MY_CONSTANT = 5
def get_constant():
return MY_CONSTANT
test_mymodule.py
import unittest
from unittest.mock import patch
import mymodule
class Test(unittest.TestCase):
#This works
@patch("mymodule.MY_CONSTANT", 3)
def test_get_constant_1(self):
self.assertEqual(mymodule.get_constant(), 3)
#This also works
def test_get_constant_2(self):
with patch("mymodule.MY_CONSTANT", 3):
self.assertEqual(mymodule.get_constant(), 3)
#But this doesn't
@patch("mymodule.MY_CONSTANT")
def test_get_constant_3(self, mock_MY_CONSTANT):
mock_MY_CONSTANT.return_value = 3
self.assertEqual(mymodule.get_constant(), 3)
#AssertionError: <MagicMock name='MY_CONSTANT' id='64980808'> != 3
My guess is I shoudln't use return_value, because mock_MY_CONSTANT is not a function. So what attribute am I supposed to use to replace the value returned when the constant is called ?
patch() unittest. mock provides a powerful mechanism for mocking objects, called patch() , which looks up an object in a given module and replaces that object with a Mock . Usually, you use patch() as a decorator or a context manager to provide a scope in which you will mock the target object.
side_effect: A function to be called whenever the Mock is called. See the side_effect attribute. Useful for raising exceptions or dynamically changing return values. The function is called with the same arguments as the mock, and unless it returns DEFAULT , the return value of this function is used as the return value.
With a module variable you can can either set the value directly or use mock. patch .
I think you're trying to learn about unit tests, mock objects, and how to replace the value of a constant in the code under test.
I'll start with your specific question about patching a constant, and then I'll describe a more general approach to replacing constant values.
Your specific question was about the difference between patch("mymodule.MY_CONSTANT", 3)
and patch("mymodule.MY_CONSTANT")
. According to the docs, the second parameter is new, and it contains the replacement value that will be patched in. If you leave it as the default, then a MagicMock
object will be patched in. As you pointed out in your question, MagicMock.return_value
works well for functions, but you're not calling MY_CONSTANT
, so the return value never gets used.
My short answer to this question is, "Don't use MagicMock
to replace a constant." If for some reason, you desperately wanted to, you could override the only thing you are calling on that constant, its __eq__()
method. (I can't think of any scenario where this is a good idea.)
import unittest
from unittest.mock import patch
import mymodule
class Test(unittest.TestCase):
#This works
@patch("mymodule.MY_CONSTANT", 3)
def test_get_constant_1(self):
self.assertEqual(mymodule.get_constant(), 3)
#This also works
def test_get_constant_2(self):
with patch("mymodule.MY_CONSTANT", 3):
self.assertEqual(mymodule.get_constant(), 3)
#This now "works", but it's a horrible idea!
@patch("mymodule.MY_CONSTANT")
def test_get_constant_3(self, mock_MY_CONSTANT):
mock_MY_CONSTANT.__eq__ = lambda self, other: other == 3
self.assertEqual(mymodule.get_constant(), 3)
Now for the more general question. I think the simplest approach is not to change the constant, but to provide a way to override the constant. Changing the constant just feels wrong to me, because it's called a constant. (Of course that's only a convention, because Python doesn't enforce constant values.)
Here's how I would handle what you're trying to do.
MY_CONSTANT = 5
def get_constant(override=MY_CONSTANT):
return override
Then your regular code can just call get_constant()
, and your test code can provide an override.
import unittest
import mymodule
class Test(unittest.TestCase):
def test_get_constant(self):
self.assertEqual(mymodule.get_constant(override=3), 3)
This can become more painful as your code gets more complicated. If you have to pass that override through a bunch of layers, then it might not be worth it. However, maybe that's showing you a problem with your design that's making the code harder to test.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With