I have an uninitialzed global variable in my module that is properly initialized during application startup. For typechecking I use the syntax var: Type
without a value from python >= 3.6 so I do not have to complicate the typechecking for the default value (which would be None
).
Now I want to unittest another function that uses this global variable but I get an error from unittest.mock.patch
. Here is a simplified version of what I am doing:
global_var: bool
#global_var: bool = False
def init(val: bool) -> None:
global global_var
global_var = val
def do_stuff() -> str:
return "a" if global_var else "b"
import unittest.mock, mod
class MockUninitializedGlobal(unittest.TestCase):
def test_it(self):
with unittest.mock.patch("mod.global_var", True):
actual = mod.do_stuff()
expected = "a"
self.assertEqual(expected, actual)
python3 -m unittest test.py
.The exception is
Traceback (most recent call last):
File "/home/luc/soquestion/test.py", line 4, in test_it
with mock.patch("mod.global_var", True):
File "/nix/store/vs4vj1yzqj1bkcqkf3b6sxm6jfy1gb4j-python3-3.7.7/lib/python3.7/unittest/mock.py", line 1323, in __enter__
original, local = self.get_original()
File "/nix/store/vs4vj1yzqj1bkcqkf3b6sxm6jfy1gb4j-python3-3.7.7/lib/python3.7/unittest/mock.py", line 1297, in get_original
"%s does not have the attribute %r" % (target, name)
AttributeError: <module 'mod' from '/home/luc/soquestion/mod.py'> does not have the attribute 'global_var'
If I comment line 1 and uncomment line 2 in mod.py the test passes.
Is there any way to make the test pass without definig a default value for the global variable in my application code?
I used the setUp
and tearDown
methods on the unittest.TestCase
class to solve this.
With my edit above about it actually being a config object the code looks like this:
class Config:
def __init__(self, filename: str):
"Load config file and set self.stuff"
self.stuff = _code_to_parse(filename)
config: Config
def do_stuff() -> str:
return "a" if config.stuff else "b"
And the test creates a mocked config object before the tests in order to mock some attribute on the config object during the test. In the teardown I also delete the object again so that nothing will spill to the next test case.
The actual mocking of the attributes I need is then done in a with
block in the actual test as it is (a) cleaner and (b) I can just mock what I need in contrast to mocking all attributes on the config object in the setUp
method.
import unittest, unittest.mock, mod
class MockUninitializedGlobal(unittest.TestCase):
def setUp(self):
mod.config = unittest.mock.Mock(spec=mod.Config)
def tearDown(self):
del mod.config
def test_it(self):
with unittest.mock.patch("mod.config.stuff", True):
actual = mod.do_stuff()
expected = "a"
self.assertEqual(expected, actual)