56

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.

3 Answers 3

86

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")
Sign up to request clarification or add additional context in comments.

5 Comments

Fantastic approach: simple and effective. Thanks for sharing. Specially useful with @Michele 's edit.
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)
Is it possible to follow a similar approach to patch a local variable in an instance method?
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).
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)
61

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

7 Comments

@badzil: Excellent. Now that you have unit-test coverage, you can remove the (need for the) global variable ;-)
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.
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.
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 how about updating the answer? I'd change my downvote to an upvote since I ended up here in 2017 :)
|
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.

4 Comments

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: 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.
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: 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.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.