Mocking subprocess with pytest-subprocess#
For apple-notes-to-sqlite I needed to write some tests that simulated executing the osascript
command using the Python subprocess
module.
I wanted my tests to run on Linux CI machines, where that command would not exist.
After failing to use unittest.mock.patch
to solve this, I went looking for alternatives. I found pytest-subprocess.
Here’s the relevant section of the test I wrote:
1from apple_notes_to_sqlite.cli import cli, COUNT_SCRIPT2
3FAKE_OUTPUT = b"""4The stuff I would expect to be returned by log lines in5my osascript script.6"""7
8def test_apple_notes_to_sqlite_dump(fp):9 fp.register_subprocess(["osascript", "-e", COUNT_SCRIPT], stdout=b"2")10 fp.register_subprocess(["osascript", "-e", fp.any()], stdout=FAKE_OUTPUT)11 runner = CliRunner()12 with runner.isolated_filesystem():13 result = runner.invoke(cli, ["--dump"])14 # ...
fp
is the fixture provided by the package (you need to pip install pytest-subprocess
for this to work).
COUNT_SCRIPT
here is the first of my osascript
constants. It looks like this (in cli.py
):
1COUNT_SCRIPT = """2tell application "Notes"3 set noteCount to count of notes4end tell5log noteCount6"""
That first fixture line says that any time my program calls osascript -e that-count-script
the return value sent to standard output should be a binary string 2
.
1fp.register_subprocess(["osascript", "-e", COUNT_SCRIPT], stdout=b"2")
The second call to subprocess
made by my script is more complicated - it involves a script that is dynamically generated.
1fp.register_subprocess(["osascript", "-e", fp.any()], stdout=FAKE_OUTPUT)
I eventually figured that using fp.any()
was easier than specifying the exact script. This is a wildcard value which matches any string. It returns the full FAKE_OUTPUT
variable as the simulated standard out.
What’s useful about pytest-subprocess
is that it works for both subprocess.check_output()
and more complex subprocess.Popen()
calls - both of which I was using in this script.