pytest-monkeypath

neevop 七月 3, 2023


testing file writes with pytest

consider a simple class with one or more methods for file write/access

from pathlib import Path

class UnderTest:
	def __init__(self, basedir=None):
		self.BASE_DIR = basedir or Path(__file__).parents

	def save_log(self):
		with open(self.BASE_DIR / "log-file.log", "a") as logfile:
			logfile.write("a dummy line")

test for it

# with the tmp_path fixture we have access to a temporary path in pytest once we can write, read, and assert over
def test_it_writes_to_log_file(tmp_path):
	under_test  = Undertest(basedir=tmp_path)
	under_test.save_log()
	file = tmp_path / "log-file.log"
	assert file.read_text() == "a dummy line"

mocking command line arguments with monkeypatch

suppose we add argument parsing to our class, which now become alse a CLI tool

import argparse
from pathlib import Path

class UnderTest:
	def __init__(self, basedir=None):
		self.BASE_DIR = basedir or Path(__file__).parents

	def _parse(self):
		ap = argparse.ArgumentParser()
		ap.add_argument("-n", "--name", required=True, help="Name of the log file")
		self.args = vars(ap.parse_args())

	def save_log(self):
		with open(self.BASE_DIR / "log-file.log", "a") as logfile:
			logfile.write("a dummy line")

if __name__ == "__main__":
	x = UnderTest()
	x.save_log()

to make our tests pass, we need to mack sys.args. For this we can use monkeypatch, another pytest fxture, which is particularly useful for mocking and patching object.

def test_it_writes_to_log_file_from_command_line_arg_(monkeypatch, tmp_path):
	monkeypatch.setattr("sys.argv", ['pytest', '--name', 'logfilename.log'])
	# test as usual here
	under_test = UnderTest(basedir=tmp_path)
	under_test.save_log()
	file = tmp_path / "logfilename.log"
	assert file.read_text() == "a dummy line"

applying fixture to every test function

when we need to patch something in our code, and this “something” must be patched inside every test function, we declare a custom fixture with @pytest.fixture and autouse

import pytest
from package.core import UnderTest  # package under test

@pytest.fixture(autouse=True)
def mock_args(monkeypatch):
	monkeypatch.setattr("sys.argv", ['pytest', '--name', 'logfilename.log'])

def test_it_writes_to_log_file(tmp_path):
	under_test = UnderTest(basedir=tmp_path)
	under_test.save_log()
	file = tmp_path / logfilename.log
	assert file.read_text() == "a dummy line"

printing print() outputs in testing

sometime we need to take a quick lookl at a variable in code, and put a print() somewhere. By default pytest suppresses this kind output. To see it during a test, run pytest with the -s flag

pytest -s