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 ?
Advertisement
Answer
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.