Pytest: mocking / monkey patching builtin input() and print() functions in python
Question:
How can I monkey patch the builtin input
and print
functions using Pytest so as to capture the output of someone else’s code and test it with pytest
before refactoring?
For example, I have acquired some code similar to this:
class QueryProcessor:
def __init__(self ...):
...
def write_search_result(self, was_found):
print('yes' if was_found else 'no')
def read_query(self):
return Query(input().split())
I don’t want to read dozens of input parameters from stdin
, and I don’t want to print
the outputs. I want to use the functions I’ve written that sift through a directory full of mytest.in
and mytest.out
files and pass the inputs to pytest
using @pytest.mark.parametrize(...)
.
But I can’t figure out how to patch the awkward read…
and write…
functions in this class.
I suspect it’s something along the lines of:
@yptest.mark.parametrize("inputs…, expected outputs…", data_reading_func())
def test_QueryProcessor(monkeypatch, inputs…, expected outputs…):
"""Docstring
"""
q = QueryProcessor()
def my_replacement_read():
...
return [...]
def my_replacement_write():
...
return [...]
monkeypatch.???
assert ...
Can you help?
Many thanks
Answers:
While awaiting a response, I came up with the following myself. I think the ideal answer will be what I’ve done implemented in the way @hoefling suggests—using patch
.
@pytest.mark.parametrize("m, n, commands, expec", helpers.get_external_inputs_outputs('spampath', helpers.read_spam_input_output))
def test_QueryProcessor(monkeypatch, m, n, commands, expec):
def mock_process_queries(cls):
for cmd in commands:
cls.process_query(Query(cmd.split())) # also mocks read_query()
def mock_write_search_result(cls, was_found):
outputs.append('yes' if was_found else 'no')
monkeypatch.setattr('test.QueryProcessor.process_queries', mock_process_queries)
monkeypatch.setattr('test.QueryProcessor.write_search_result', mock_write_search_result)
outputs = []
proc = QueryProcessor(m)
proc.process_queries()
assert outputs == expec
UPDATE:
@pytest.mark.parametrize("m, n, commands, expec",
helpers.get_external_inputs_outputs(
'spampath',
helpers.read_input_output))
def test_QueryProcessor_mockpatch(m, n, commands, expec):
commands.insert(0,n)
mock_stdout = io.StringIO()
with patch('spammodule.input', side_effect=commands):
with patch('sys.stdout', mock_stdout):
proc = hash_chains.QueryProcessor(m)
proc.process_queries()
assert mock_stdout.getvalue().split('n')[:-1] == expec
Hey there I guess it is too late for you but for the other people asking themselves how to monkeypatch input(), I did it like this:
monkeypatch.setattr(builtins, 'input', lambda *args, **kwargs: 'Yes, I like monkeypatching')
So I would refactor the code you posted in your own answer its update to (Assuming that commands is callable, since you specified it as a side_effect):
# don't forget the imports
import builtins
import io
import sys
@pytest.mark.parametrize("m, n, commands, expec",
helpers.get_external_inputs_outputs('spampath',helpers.read_input_output))
def test_QueryProcessor_mockpatch(monkeypatch, m, n, commands, expec):
commands.insert(0,n)
mock_stdout = io.StringIO()
monkeypatch.setattr(builtins, 'input', lambda description: commands())
monkeypatch.setattr(sys, 'stdout', mock_stdout)
proc = hash_chains.QueryProcessor(m)
proc.process_queries()
assert mock_stdout.getvalue().split('n')[:-1] == expec
How can I monkey patch the builtin input
and print
functions using Pytest so as to capture the output of someone else’s code and test it with pytest
before refactoring?
For example, I have acquired some code similar to this:
class QueryProcessor:
def __init__(self ...):
...
def write_search_result(self, was_found):
print('yes' if was_found else 'no')
def read_query(self):
return Query(input().split())
I don’t want to read dozens of input parameters from stdin
, and I don’t want to print
the outputs. I want to use the functions I’ve written that sift through a directory full of mytest.in
and mytest.out
files and pass the inputs to pytest
using @pytest.mark.parametrize(...)
.
But I can’t figure out how to patch the awkward read…
and write…
functions in this class.
I suspect it’s something along the lines of:
@yptest.mark.parametrize("inputs…, expected outputs…", data_reading_func())
def test_QueryProcessor(monkeypatch, inputs…, expected outputs…):
"""Docstring
"""
q = QueryProcessor()
def my_replacement_read():
...
return [...]
def my_replacement_write():
...
return [...]
monkeypatch.???
assert ...
Can you help?
Many thanks
While awaiting a response, I came up with the following myself. I think the ideal answer will be what I’ve done implemented in the way @hoefling suggests—using patch
.
@pytest.mark.parametrize("m, n, commands, expec", helpers.get_external_inputs_outputs('spampath', helpers.read_spam_input_output))
def test_QueryProcessor(monkeypatch, m, n, commands, expec):
def mock_process_queries(cls):
for cmd in commands:
cls.process_query(Query(cmd.split())) # also mocks read_query()
def mock_write_search_result(cls, was_found):
outputs.append('yes' if was_found else 'no')
monkeypatch.setattr('test.QueryProcessor.process_queries', mock_process_queries)
monkeypatch.setattr('test.QueryProcessor.write_search_result', mock_write_search_result)
outputs = []
proc = QueryProcessor(m)
proc.process_queries()
assert outputs == expec
UPDATE:
@pytest.mark.parametrize("m, n, commands, expec",
helpers.get_external_inputs_outputs(
'spampath',
helpers.read_input_output))
def test_QueryProcessor_mockpatch(m, n, commands, expec):
commands.insert(0,n)
mock_stdout = io.StringIO()
with patch('spammodule.input', side_effect=commands):
with patch('sys.stdout', mock_stdout):
proc = hash_chains.QueryProcessor(m)
proc.process_queries()
assert mock_stdout.getvalue().split('n')[:-1] == expec
Hey there I guess it is too late for you but for the other people asking themselves how to monkeypatch input(), I did it like this:
monkeypatch.setattr(builtins, 'input', lambda *args, **kwargs: 'Yes, I like monkeypatching')
So I would refactor the code you posted in your own answer its update to (Assuming that commands is callable, since you specified it as a side_effect):
# don't forget the imports
import builtins
import io
import sys
@pytest.mark.parametrize("m, n, commands, expec",
helpers.get_external_inputs_outputs('spampath',helpers.read_input_output))
def test_QueryProcessor_mockpatch(monkeypatch, m, n, commands, expec):
commands.insert(0,n)
mock_stdout = io.StringIO()
monkeypatch.setattr(builtins, 'input', lambda description: commands())
monkeypatch.setattr(sys, 'stdout', mock_stdout)
proc = hash_chains.QueryProcessor(m)
proc.process_queries()
assert mock_stdout.getvalue().split('n')[:-1] == expec