Reputation: 91
How do I implement pytest for a function with @contextlib.contextmanager
?
In order to improve the coverage, I want to have a test for this function as well.
@contextlib.contextmanager
def working_directory_relative_to_script_location(path):
"""Changes working directory, and returns to previous on exit. It's needed for PRAW for example,
because it looks for praw.ini in Path.cwd(), but I have that file in the settings directory.
"""
prev_cwd = Path.cwd()
script_dir = Path(os.path.realpath(__file__)).parent
os.chdir(script_dir / path)
try:
yield
finally:
os.chdir(prev_cwd)
Upvotes: 0
Views: 740
Reputation: 755
This answer does not strictly solve the title of the question but it offers a better solution for the typical problem it addresses.
You can easily change to a directory in a test context creating a fixture.
@fixture(scope="module", autouse=True, params=["./settings"])
def in_settings_folder(request.param):
prevdir = os.getcwd()
os.chdir(request.param)
yield
os.chdir(prevdir)
def test_settings_1(): # This pass
print(f"{os.getcwd()}") # /.../project/settings
assert check_settings()
def test_settings_2(): # This fails
print(f"{os.getcwd()}") # /.../project/settings
raise Exception
def test_settings_3(): # This pass
print(f"{os.getcwd()}") # /.../project/settings
assert check_settings()
I changed it a bit to use tmpdir, but you will see something like:
$ pytest -s tests/test_tests.py
...
tests/test_tests.py /tmp/pytest-of-borja/pytest-52/settings
./tmp/pytest-of-borja/pytest-52/settings
F/tmp/pytest-of-borja/pytest-52/settings
.
...
Where you can see on the 3 tests, even if 1 failed, the cwd
is maintained.
You can decide when this directory "switch" will happen using the fixture scope
. Note if you use autouse
with session
all the tests will run in that directory.
You can use scope=module
if you want to apply it to all tests in the module where it is called or scope=class
inside a Class
to limit it to the test inside the class:
class TestSettings:
@fixture(scope="class", autouse=True, params=["./settigns"])
def in_settings_folder(self, tmpdir):
prevdir = os.getcwd()
os.chdir(tmpdir)
yield
os.chdir(prevdir)
def test_settings_1(self):
print(f"{os.getcwd()}") # /.../project/settings
assert check_settings()
def test_settings_2(): # /.../project
print(f"{os.getcwd()}")
assert check_settings()
If your fixtures are generating data which you want to check on the tests, then use tmpdir_factory
to create a working directory.
@fixture(scope="module", autouse=True)
def tmpdir(tmpdir_factory):
return tmpdir_factory.mktemp("settings")
class TestSettings:
@fixture(scope="class", autouse=True)
def in_settings_folder(self, tmpdir):
prevdir = os.getcwd()
os.chdir(tmpdir)
yield
os.chdir(prevdir)
def test_settings(self):
print(f"{os.getcwd()}") # /tmp/pytest-of-x/pytest-1/settings0
assert check_settings()
Upvotes: 0
Reputation: 91
Shortest version:
def test_working_directory_relative_to_script_location(tmpdir):
initial_path = Path.cwd()
@working_directory_relative_to_script_location(tmpdir)
def with_decorator():
return Path.cwd()
try:
assert with_decorator() == tmpdir
Upvotes: 0
Reputation: 3483
Maybe not the nicest solution because it actually creates directories on your drive:
import contextlib
import os
from pathlib import Path
@contextlib.contextmanager
def working_directory_relative_to_script_location(path):
"""Changes working directory, and returns to previous on exit.
It's needed for PRAW for example,
because it looks for praw.ini in Path.cwd(),
but I have that file in the settings directory."""
prev_cwd = Path.cwd()
script_dir = Path(os.path.realpath(__file__)).parent
os.chdir(script_dir / path)
try:
yield
finally:
os.chdir(prev_cwd)
def test_decorator():
tmp = 'tmp_dir'
initial_path = Path.cwd()
os.mkdir(tmp)
tmp_path = os.path.join(initial_path, tmp)
@working_directory_relative_to_script_location(tmp_path)
def with_decorator():
return Path.cwd()
try:
assert with_decorator() == tmp_path
assert Path.cwd() == initial_path
except AssertionError as e:
raise e
finally:
os.rmdir(tmp)
test_decorator()
Here, I created a function that returns the current working directory and decorated it with your context manager. What one would expect from your context manager is that it changes the directory to tmp
during the function invocation (this is tested by the first assert
statement) and that it changes it back to the initial directory afterwards (this is tested by the second assert
statement).
Upvotes: 1