I have a program that uses the Python fileinput
module, and I am trying to write unittests for the main()
function. They work find when using an actual file, but raise OSError: reading from stdin while output is captured
when I try to pass data via stdin. What is the correct way to mock the stdin input when using fileinput
?
Example my_fileinput.py
:
"""
$ echo "42" | python3.8 my_fileinput.py -
answer: 42
"""
import fileinput
import sys
def main():
for line in fileinput.input(sys.argv[1:]):
sys.stdout.write(f"answer #{fileinput.lineno()}: {line}")
if __name__ == "__main__":
main()
Example test_my_fileinput.py
:
"""
$ python3.10 -m pytest test_my_fileinput.py
OSError: reading from stdin while output is captured
"""
import io
from unittest import mock
import my_fileinput
def test_stdin():
"""Test fileinput with stdin."""
with mock.patch.object(my_fileinput, "raw_input", create=True, return_value="42"):
with mock.patch("sys.stdout", new=io.StringIO()) as stdout:
with mock.patch("sys.argv", ["my_fileinput.py", "-"]):
# Raises OSError: reading from stdin while output is captured
my_fileinput.main()
assert stdout.getvalue() == "answer: 42\n"
I have tried various ways of mocking stdin, all with the same results. All result in the same OSError.
CodePudding user response:
It is not necessary for you to test fileinput
itself, since that will be tested by CPython's own test suite: Lib/test/test_fileinput.py
. The pytesthonic way to test your code would be like this, using fixtures:
import my_fileinput
def test_stdin(mocker, capsys):
mocker.patch("fileinput.input", return_value=["42\n"])
my_fileinput.main()
out, err = capsys.readouterr()
assert out == "answer: 42\n"
The capsys
fixture is included with pytest, and the mocker
fixture is provided by plugin pytest-mock.
CodePudding user response:
Changing the first two arguments of mock.patch.object
to fileinput
and "input"
seems to fix the OSError
.
with mock.patch.object(fileinput, "input", create=True, return_value="42"):
The first argument is the target object you want to patch, which is the fileinput
module. The second argument is the attribute to be changed in target object, which is input
.
import io
import fileinput
from unittest import mock
import my_fileinput
def test_stdin():
"""Test fileinput with stdin."""
with mock.patch.object(fileinput, "input", create=True, return_value="42"):
with mock.patch("sys.stdout", new=io.StringIO()) as stdout:
with mock.patch("sys.argv", ["my_fileinput.py", "-"]):
my_fileinput.main()
assert stdout.getvalue() == "answer: 42\n"
Update: A different version patching sys.stdin
instead of inputfile.input
import io
from unittest import mock
import my_fileinput
def test_stdin():
"""Test fileinput with stdin."""
with mock.patch("sys.stdin", new=io.StringIO("42")):
with mock.patch("sys.stdout", new=io.StringIO()) as stdout:
with mock.patch("sys.argv", ["my_fileinput.py", "-"]):
my_fileinput.main()
assert stdout.getvalue() == "answer: 42\n"