I am attempting to unit test some Python 3 code that imports a module. Unfortunately, the way the module is written, simply importing it has unpleasant side effects, which are not important for the tests. I'm trying to use unitest.mock.patch
to get around it, but not having much luck.
Here is the structure of an illustrative sample:
.
└── work
├── __init__.py
├── test_work.py
├── work.py
└── work_caller.py
__init__.py
is an empty file
import os
def work_on():
path = os.getcwd()
print(f"Working on {path}")
return path
def unpleasant_side_effect():
print("I am an unpleasant side effect of importing this module")
# Note that this is called simply by importing this file
unpleasant_side_effect()
from work.work import work_on
class WorkCaller:
def call_work(self):
# Do important stuff that I want to test here
# This call I don't care about in the test, but it needs to be called
work_on()
from unittest import TestCase, mock
from work.work_caller import WorkCaller
class TestWorkMockingModule(TestCase):
def test_workcaller(self):
with mock.patch("work.work.unpleasant_side_effect") as mocked_function:
sut = WorkCaller()
sut.call_work()
In work_caller.py
I only want to test the beginning code, not the call to work_on()
. When I run the test, I get the following output:
paul-> python -m unittest
I am an unpleasant side effect of importing this module
Working on /Users/paul/src/patch-test
.
----------------------------------------------------------------------
Ran 1 test in 0.000s
OK
I was expecting that the line I am an unpleasant side effect of importing this module
would not be printed because the function unpleasant_side_effect
would be mocked. Where might I be going wrong?
Avoid Test Interdependence You, therefore, cannot count on the test suite or the class that you're testing to maintain state in between tests. But that won't always make itself obvious to you. If you have two tests, for instance, the test runner may happen to execute them in the same order each time.
Tightly coupled code is extremely hard to unit test (and probably shouldn't be unit tested - it should be re-factored first), because by definition unit tests can only test a specific unit of something. All calls to databases or other components of the system should be avoided from Unit Tests because they violate this.
In this method, we will use the unittest module and filename to run the tests. The command to run the tests is python -m unittest filename.py .
The unpleasant_side_effect
is run for two reasons. First because the imports are handled before the test case is started and is therefore not mocked when importing is happening. Secondly, because the mocking itself imports work.py
and thus runs unpleasant_side_effect
even if work_caller.py
was not imported.
The import problem can be solved by mocking the module work.py
itself. This can either be done globally in the test module or in the testcase itself. Here I assigned it a MagicMock
, which can be imported, called etc.
from unittest import TestCase, mock
class TestWorkMockingModule(TestCase):
def test_workcaller(self):
import sys
sys.modules['work.work'] = mock.MagicMock()
from work.work_caller import WorkCaller
sut = WorkCaller()
sut.call_work()
The downside is that work_on is also mocked, which I am not sure whether is a problem in your case.
It is not possible to not run the entire module when it is imported, since functions and classes are also statements, thus the module execution has to finish before returning to the caller, where one want to alter the imported module.
In case you asked partially about the best practice.
You should always split your code to library used by every other code and side-effect lines. And probably eliminate side-effects by calling the side-effecting code from you def main():
But if you want to keep side-effects anyway, then you could do:
work_lib.py
:
...no side-effects...
work.py
from work_lib import ...
...side-effects....
test_work.py
from work_lib import ...
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With