Reputation: 1091
I'm developing a project that has the following architecture:
|-- weather
| |-- __init__.py
| |-- weather.py
|-- library
| |-- data.json
|-- test
| |-- __init__.py
| |-- test_weather.py
I would like to test that performing a save creates the library
directory if it does not already exist. test_weather.py
contains:
import os
import shutil
from weather import weather
def test_library_creation():
# Initialize.
app = weather.WeatherApp()
temp_data = app.get_data()
# Library should not exist initially.
assert not os.path.isdir('library')
app.save(temp_data)
# Library should exist after save.
assert os.path.isdir('library')
assert os.path.isfile(os.path.join('library', 'data.json'))
# Cleanup.
shutil.rmtree('library')
However, I may have some data saved in data.json
that I don't want to delete as a result of running this test.
Does pytest
have a solution for this case?
weather.py
contains:
import os
import json
DEFAULT_SAVE_PATH = os.path.join('library', 'data.json')
class WeatherApp:
def get_data(self):
return dict(temperature=25, humidity=0.5)
def save(self, data, save_path=DEFAULT_SAVE_PATH):
with open('data.json', 'w') as jf:
json.dump(data, jf)
os.renames('data.json', save_path)
Upvotes: 2
Views: 1570
Reputation: 66431
Since you have already thought about providing a custom save path, you don't even need to mock anything if you don't want to; just pass the custom path derived from the tmp_path
fixture:
def test_library_creation(tmp_path):
app = weather.WeatherApp()
temp_data = app.get_data()
lib_dir = tmp_path / 'library'
# this check is somewhat redundant because pytest will ensure
# the temp dir is created from scratch and is empty
assert not lib_dir.is_dir()
app.save(temp_data, save_path=str(lib_dir / 'data.json'))
# Library should exist after save.
assert lib_dir.is_dir()
assert (lib_dir / 'data.json').is_file()
shutil.rmtree(str(lib_dir))
Some notes:
The string conversion of path-like objects is required only on Python versions older than 3.6; if you use 3.6 or 3.7, you can work directly with path-like objects, e.g.
app.save(temp_data, save_path=lib_dir / 'data.json')
or
shutil.rmtree(lib_dir)
Beware that os.rename
/os.renames
are not error-prone against changing filesystems, e.g. you write data.json
on a local ext4 partition and save_path
points to a CIFS share, and here comes the error.
Maybe the renaming operation is redundant? You can write the data directly to save_path
. You just need to ensure the target dir exists first, e.g. with os.makedirs(os.path.dirname(save_path), exist_ok=True)
.
If an assertion fails in the test, the line shutil.rmtree(str(lib_dir))
won't be executed; this is no big deal since the tmp_path
is created on tmpfs and will be removed after the next reboot anyway. However, if you want to handle the deletion yourself, I would do this in the test teardown using a custom fixture:
import os
import shutil
import pytest
from weather import weather
@pytest.fixture
def lib_dir(tmp_path):
d = tmp_path / 'library'
yield d
shutil.rmtree(str(d))
def test_library_creation(lib_dir):
app = weather.WeatherApp()
temp_data = app.get_data()
assert not lib_dir.is_dir()
app.save(temp_data, save_path=str(lib_dir))
# Library should exist after save.
assert lib_dir.is_dir()
assert (lib_dir / 'data.json').is_file()
Now the lib_dir
will be removed regardless whether the test passes or not.
If you want to test the default arguments (the app.save(temp_data)
case), you will need to monkeypatch the DEFAULT_SAVE_PATH
constant in the weather
module. It's pretty easy using the monkeypatch
fixture:
def test_library_creation(monkeypatch, lib_dir):
app = weather.WeatherApp()
temp_data = app.get_data()
assert not lib_dir.is_dir()
with monkeypatch.context() as m:
m.setattr(weather, 'DEFAULT_SAVE_PATH', os.path.join(lib_dir, 'data.json'))
app.save(temp_data)
# Library should exist after save.
assert lib_dir.is_dir()
assert (lib_dir / 'data.json').is_file()
Upvotes: 3