Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to patch a constant in Python using a mock as function parameter

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 ?

like image 485
Kévin Barré Avatar asked Feb 16 '17 13:02

Kévin Barré


People also ask

What is patch in Python mock?

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.

What is Side_effect in mock Python?

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.

Can you mock a variable Python?

With a module variable you can can either set the value directly or use mock. patch .


1 Answers

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.

like image 132
Don Kirkby Avatar answered Oct 16 '22 19:10

Don Kirkby