Mocking a global variable in pytest

Question:

how do you mock a global variable in pytest? Here is a pair of example files:

File being tested, call it main.py:

MY_GLOBAL = 1 
def foo():
    return MY_GLOBAL*2

def main()
    # some relevant invokation of foo somewhere here

if __name__=='__main__':
    main()

File that is testing, call it test_main.py:

from main import foo

class TestFoo(object):
    def test_that_it_multiplies_by_global(self):
        # expected=2, we could write, but anyway ...
        actual = foo()
        assert actual == expected

This is just a dummy example of course, but how would you go about mocking MY_GLOBAL and giving it another value?
Thanks in advance i’ve been kind of breaking my head over this and i bet it’s really obvious.

Asked By: user19138502

||

Answers:

The global variable is an attribute of the module, which you can patch using patch.object:

import main
from unittest.mock import patch

class TestFoo(object):
    def test_that_it_multiplies_by_global(self):
        with patch.object(main, 'MY_GLOBAL', 3):
            assert main.foo(4) == 12  # not 4

However, you want to make sure you are testing the right thing. Is foo supposed to multiply its argument by 1 (and the fact that it uses a global variable with a value of 1 an implementation detail), or is it supposed to multiply its argument by whatever value MY_GLOBAL has at the time of the call? The answer to that question will affect how you write your test.

Answered By: chepner

It’s useful to distinguish between module-level constants and global variables. The former are pretty common, but the latter are an anti-pattern in Python. You seem to have a module-level constant (read-only access to the var in normal production code). Global variables (R/W access in production code) should generally be refactored if possible.

For module constants:
If you can do so, it’s generally more maintainable to refactor the functions that depend on module constants. This allows direct testing with alternate values as well as a single source of truth for the "constants" and backward compatibility. A minimal refactor is as simple as adding an optional parameter in each function that depends on the "constant" and doing a simple search-and-replace in that function, e.g.:

 def foo(value=MY_GLOBAL):
     return value*2

All other code can continue to call foo() as normal, but if you want to write tests with alternate values of MY_GLOBAL, you can simply call foo(value=7484).

If what you want is an actual global, (with the global keyword and read/write access during production code, try these alternatives.

Answered By: Sarah Messer
Categories: questions Tags: , ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.