55

I am working on a series of unit tests in Python, some of which depend on the value of a configuration variable. These variables are stored in a global Python config file and are used in other modules. I would like to write unit tests for different values of the configuration variables but have not yet found a way to do this.

I do not have the possibility to rewrite the signatures of the methods I'm testing.

This is what I would like to achieve:

from my_module import my_function_with_global_var

class TestSomething(self.unittest):

    def test_first_case(self):
         from config import MY_CONFIG_VARIABLE
         MY_CONFIG_VARIABLE = True
         self.assertEqual(my_function_with_global_var(), "First result")

    def test_second_case(self):
         from config import MY_CONFIG_VARIABLE
         MY_CONFIG_VARIABLE = False
         self.assertEqual(my_function_with_global_var(), "Second result")

Thanks.

Edit: Made the example code more explicite.

badzil
  • 3,440
  • 4
  • 19
  • 27

3 Answers3

83

You probably want to mock those global variables instead. The advantage of this is that the globals get reset once you're done. Python ships with a mocking module that lets you do this.

unittest.mock.patch be used as a decorator:

class TestSomething(self.unittest):

    @patch('config.MY_CONFIG_VARIABLE', True)
    def test_first_case(self):
         self.assertEqual(my_function_with_global_var(), "First result")

You can also use it as a context manager:

    def test_first_case(self):
        with patch('config.MY_CONFIG_VARIABLE', True):
            self.assertEqual(my_function_with_global_var(), "First result")
Michele d'Amico
  • 22,111
  • 8
  • 69
  • 76
Flimm
  • 136,138
  • 45
  • 251
  • 267
  • Fantastic approach: simple and effective. Thanks for sharing. Specially useful with @Michele 's edit. – fedorqui Mar 09 '17 at 14:21
  • 1
    In my case I didn't care about the initial value, but needed to verify the global was written to. That's possible using `with patch('config') as mock_config` and `self.assertEqual(True, mock_config.MY_CONFIG_VARIABLE)` – codehearts Nov 11 '17 at 23:18
  • Is it possible to follow a similar approach to patch a local variable in an instance method? – Naveen Apr 12 '18 at 04:07
  • 1
    This does not seem to work when the global variable is used as a function's default parameter value, e.g. `def my_function_with_global_var(s=MY_CONFIG_VARIABLE)`. – sshow Aug 25 '18 at 23:06
  • This didn't work for me because my function under test was importing the global var in a different module. I was patching the module that defined the global, but you have to patch the module that imports the global (in my case the module with the function I was testing) – Alan Jun 03 '22 at 07:12
62

Use unittest.mock.patch as in @Flimm's answer, if that's available to you.


Original Answer

Don't do this:

from my_module import my_function_with_global_var

But this:

import my_module

And then you can inject MY_CONFIG_VARIABLE into the imported my_module, without changing the system under test like so:

class TestSomething(unittest.TestCase): # Fixed that for you!

    def test_first_case(self):
         my_module.MY_CONFIG_VARIABLE = True
         self.assertEqual(my_module.my_function_with_global_var(), "First result")

    def test_second_case(self):
         my_module.MY_CONFIG_VARIABLE = False
         self.assertEqual(my_module.my_function_with_global_var(), "Second result")

I did something similar in my answer to How can I simulate input to stdin for pyunit? .

johnsyweb
  • 136,902
  • 23
  • 188
  • 247
  • 1
    @badzil: Excellent. Now that you have unit-test coverage, you can remove the (need for the) global variable ;-) – johnsyweb Jun 09 '11 at 00:39
  • But is there any cons to have global variables in python to define constants ? I read that if yoy put them in the `__init__` of the module you import it is okay. – Kartoch Aug 16 '13 at 13:02
  • But how would you get the variable to automatically change back to the original value after the test, like with `unittest.mock.patch`? – Hubro Aug 23 '15 at 18:16
  • 31
    This approach opens you up to a subtle bug, because the value of `my_module.MY_CONFIG_VARIABLE` isn't reset when the test completes. If subsequent tests run in the same process use `MY_CONFIG_VARIABLE` without expecting it to be changed, your tests may fail or pass based on the order they're executed. – ForeverWintr Aug 29 '16 at 23:38
  • @ForeverWintr this isn't that subtle. It's a major drawback. – Reut Sharabani Jan 02 '17 at 14:15
  • 8
    You're right. This is subtle but major drawback and is not the way I'd do things in 2017. I'm not sure mock.patch even shipped with Python in 2011 when I wrote this answer. – johnsyweb Jan 02 '17 at 21:57
  • 1
    @Johnsyweb how about updating the answer? I'd change my downvote to an upvote since I ended up here in 2017 :) – Reut Sharabani Jan 03 '17 at 09:57
2

You code imports MY_CONFIG_VARIABLE into the local scope and then immediately overwrites the name with a different object. That won't change the value in the config module. Try

import config
config.MY_CONFIG_VARIABLE = False

instead.

Sven Marnach
  • 574,206
  • 118
  • 941
  • 841
  • 2
    Maybe I forgot to mention something else. The functions I'm testing are in a separate module and therefore have already imported the global variable I need to modify. Your proposed modification only modifies the global variable in the test file. – badzil Jun 07 '11 at 16:28
  • 1
    @badzil: Probably the best option is not to use `from config import MY_CONFIG_VARIABLE` in the modules you are testing, but rather `import config` and access the variables as `config.MY_CONFIG_VARIABLE`. – Sven Marnach Jun 07 '11 at 17:52
  • Thanks. This works although I was looking to not modify the module I am testing. I'm guessing that as the module to test uses the import ... from ... directive, there is no way to change the value of the global variable after the import. – badzil Jun 07 '11 at 18:28
  • @badzil: If the value is immutable (like the `bool` in the example), than you can't change it. That's somehow the nature of immutable objects. -- Another option is to `reload()` and reimport all modules after changing the value in `config`. – Sven Marnach Jun 07 '11 at 18:36