I have something like this in my source file
# code.py
def some_func():
# doing some connections and stuff
return {'someKey': 'someVal'}
class ClassToTest:
var = some_func()
My test file looks like this... I am trying to mock some_func
as I want to avoid creating connections.
# test_code.py
from src.code import ClassToTest
def mock_function():
return {"someOtherKey": "someOtherValue"}
class Test_Code(unittest.TestCase):
@mock.patch('src.code.some_func', new=mock_function)
def test_ClassToTest(self):
self.assertEqual(ClassToTest.var, {"someOtherKey": "someOtherValue"})
But this doesn't work. On the other hand if var
is an instant variable mock works fine. I guess this is due to class variables getting initialized during imports. How do I properly mock some_func
before var
even gets initialized?
CodePudding user response:
I didn't really understand the need for you to have this mock and test it in this way, but you can try this approach:
#code.py
def some_func():
# doing some connections and stuff
return {'someKey': 'someVal'}
class ClassToTest:
def __init__(self) -> None:
self.var = some_func()
import code
import unittest
from unittest import mock
def mock_function():
return {"someOtherKey": "someOtherValue"}
class Test_Code(unittest.TestCase):
@mock.patch.object(code, attribute='some_func', new=mock_function)
def test_ClassToTest(self):
self.assertEqual(code.ClassToTest().var, {"someOtherKey": "someOtherValue"})
if __name__ == '__main__':
unittest.main()
CodePudding user response:
When you imported code.py
, there is no active patch yet, so when ClassToTest.var
was initialized, it used the original some_func
. Only then would the patch for src.code.some_func
would take effect which obviously is too late now.
Solution 1
What you can do is to patch some_func
and then reload code.py
so that it re-initializes the ClassToTest
including its attribute var
. Thus since we already have an active patch by the time we reload code.py
, then ClassToTest.var
would be set with the patched value.
- But we can't do it if both the class and the patched function lives in the same file, so to make it testable move
some_func
to another file and then just import it.
src/code.py
from src.other import some_func
class ClassToTest:
var = some_func()
src/other.py
def some_func():
# doing some connections and stuff
return {'realKey': 'realValue'}
test_code.py
from importlib import reload
import sys
import unittest
from unittest import mock
from src.code import ClassToTest # This will always refer to the unpatched version
def mock_function():
return {"someOtherKey": "someOtherValue"}
class Test_Code(unittest.TestCase):
def test_real_first(self):
self.assertEqual(ClassToTest.var, {"realKey": "realValue"})
@mock.patch('src.other.some_func', new=mock_function)
def test_mock_then_reload(self):
# Option 1:
# import src
# reload(src.code)
# Option 2
reload(sys.modules['src.code'])
from src.code import ClassToTest # This will be the patched version
self.assertEqual(ClassToTest.var, {"someOtherKey": "someOtherValue"})
def test_real_last(self):
self.assertEqual(ClassToTest.var, {"realKey": "realValue"})
Output
$ pytest -q
... [100%]
3 passed in 0.04s
Solution 2
If you don't want the real some_func
to be ever called during test, then just reloading isn't enough. What needs to be done is to never import the file containing ClassToTest
nor any file that would import it indirectly. Only import it once an active patch for some_func
is already established.
from importlib import reload
import sys
import unittest
from unittest import mock
# from src.code import ClassToTest # Remove this import!!!
def mock_function():
return {"someOtherKey": "someOtherValue"}
class Test_Code(unittest.TestCase):
@mock.patch('src.other.some_func', new=mock_function)
def test_mock_then_reload(self):
from src.code import ClassToTest # Move the import here once the patch has taken effect already
self.assertEqual(ClassToTest.var, {"someOtherKey": "someOtherValue"})