Mocking a global variable

PythonUnit TestingMockingPatch

Python Problem Overview


I've been trying to implement some unit tests for a module. An example module named alphabet.py is as follows:

import database

def length_letters():
    return len(letters)

def contains_letter(letter):
    return True if letter in letters else False


letters = database.get('letters')   # returns a list of letters

I'd like to mock the response from a database with some values of my choice, but the code below doesn't seem to work.

import unittests  
import alphabet   
from unittest.mock import patch   
  
  
class TestAlphabet(unittest.TestCase): 
    @patch('alphabet.letters')
    def setUp(self, mock_letters):
        mock_letters.return_value = ['a', 'b', 'c']   
  
    def test_length_letters(self):
        self.assertEqual(3, alphabet.length_letters())
      
    def test_contains_letter(self):   
        self.assertTrue(alphabet.contains_letter('a'))

I have seen many examples in which 'patch' is applied to methods and classes, but not to variables. I prefer not to patch the method database.get because I may use it again with different parameters later on, so I would need a different response.

What am I doing wrong here?

Python Solutions


Solution 1 - Python

Variables can be patched as follows:

from mock import patch
@patch('module.variable', new_value)    

For example:

import alphabet
from mock import patch

@patch('alphabet.letters', ['a', 'b', 'c'])
class TestAlphabet():

    def test_length_letters(self):
        assert 3 == alphabet.length_letters()

    def test_contains_letter(self):
        assert alphabet.contains_letter('a')

Solution 2 - Python

Try this:

import unittests  
import alphabet   
from unittest import mock 


class TestAlphabet(unittest.TestCase): 
    def setUp(self):
        self.mock_letters = mock.patch.object(
            alphabet, 'letters', return_value=['a', 'b', 'c']
        )

    def test_length_letters(self):
        with self.mock_letters:
            self.assertEqual(3, alphabet.length_letters())

    def test_contains_letter(self):
        with self.mock_letters:
            self.assertTrue(alphabet.contains_letter('a'))

You need to apply the mock while the individual tests are actually running, not just in setUp(). We can create the mock in setUp(), and apply it later with a with ... Context Manager.

Solution 3 - Python

If you are using pytest-mock (see https://pypi.org/project/pytest-mock/), then all you need to do is use the built in fixture.

def test_my_function(mocker):
    # Mock the value of global variable `MY_NUMBER` as 10
    mocker.patch("path.to.file.MY_NUMBER", 10)
    # rest of test...

Solution 4 - Python

I ran into a problem where I was trying to mock out variables that were used outside of any function or class, which is problematic because they are used the moment you try to mock the class, before you can mock the values.

I ended up using an environment variable. If the environment variable exists, use that value, otherwise use the application default. This way I could set the environment variable value in my tests.

In my test, I had this code before the class was imported

os.environ["PROFILER_LOG_PATH"] = "./"

In my class:

log_path = os.environ.get("PROFILER_LOG_PATH",config.LOG_PATH)

By default my config.LOG_PATH is /var/log/<my app name>, but now when the test is running, the log path is set to the current directory. This way you don't need root access to run the tests.

Solution 5 - Python

You don't need to use mock. Just import the module and alter the value of the global within setUp():

import alphabet

class TestAlphabet(unittest.TestCase): 
   def setUp(self):
        alphabet.letters = ['a', 'b', 'c']

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionFunkaticView Question on Stackoverflow
Solution 1 - PythonValera ManiukView Answer on Stackoverflow
Solution 2 - PythonWillView Answer on Stackoverflow
Solution 3 - PythonAli SajjadView Answer on Stackoverflow
Solution 4 - PythonRuth Grace WongView Answer on Stackoverflow
Solution 5 - PythonJohn GordonView Answer on Stackoverflow