How to test a function with input call?
As The Compiler suggested, pytest has a new monkeypatch fixture for this. A monkeypatch object can alter an attribute in a class or a value in a dictionary, and then restore its original value at the end of the test.
In this case, the built-in input
function is a value of python's __builtins__
dictionary, so we can alter it like so:
def test_something_that_involves_user_input(monkeypatch): # monkeypatch the "input" function, so that it returns "Mark". # This simulates the user entering "Mark" in the terminal: monkeypatch.setattr('builtins.input', lambda _: "Mark") # go about using input() like you normally would: i = input("What is your name?") assert i == "Mark"
You should probably mock the built-in input
function, you can use the teardown
functionality provided by pytest
to revert back to the original input
function after each test.
import module # The module which contains the call to inputclass TestClass: def test_function_1(self): # Override the Python built-in input method module.input = lambda: 'some_input' # Call the function you would like to test (which uses input) output = module.function() assert output == 'expected_output' def test_function_2(self): module.input = lambda: 'some_other_input' output = module.function() assert output == 'another_expected_output' def teardown_method(self, method): # This method is being called after each test case, and it will revert input back to original function module.input = input
A more elegant solution would be to use the mock
module together with a with statement
. This way you don't need to use teardown and the patched method will only live within the with
scope.
import mockimport moduledef test_function(): with mock.patch.object(__builtins__, 'input', lambda: 'some_input'): assert module.function() == 'expected_output'
You can replace sys.stdin
with some custom Text IO, like input from a file or an in-memory StringIO buffer:
import sysclass Test: def test_function(self): sys.stdin = open("preprogrammed_inputs.txt") module.call_function() def setup_method(self): self.orig_stdin = sys.stdin def teardown_method(self): sys.stdin = self.orig_stdin
this is more robust than only patching input()
, as that won't be sufficient if the module uses any other methods of consuming text from stdin.
This can also be done quite elegantly with a custom context manager
import sysfrom contextlib import contextmanager@contextmanagerdef replace_stdin(target): orig = sys.stdin sys.stdin = target yield sys.stdin = orig
And then just use it like this for example:
with replace_stdin(StringIO("some preprogrammed input")): module.call_function()