0

First of all, -s didn't help, the test runs forever with it.
I have a test for one function, it looks like this:

def test(monkeypatch):
    ...
    monkeypatch.setitem(__builtins__, 'input', make_multiple_inputs(input_lines))
    sys.stdout = mystdout = StringIO()
    ...
    actual = mystdout.getvalue()

It works if the tested function contains input() but doesn't work if it contains sys.stdin.readline(). How do I make it work with the latter?

2 Answers2

1

Had the same issue, and I couldn't get the setitem to work.

I ended up following @Felk's answer from here How to test a function with input call?. I have also copied below a very slightly modified version, and a test I wrote to check it was all working.

import sys
from io import StringIO
from contextlib import contextmanager

@contextmanager
def replace_stdin(target: str):
    """ The provided input should be the text the user inputs. It support multiple lines for multiple inputs """
    orig = sys.stdin
    sys.stdin = StringIO(target)
    yield
    sys.stdin = orig

# Some example tests for the context manager

txt1 = """a
b
c"""

txt2 = """a"""

def test_input_mocking():
    with replace_stdin(txt1):
        assert input() == txt1.split("\n")[0]
        assert input("something") == txt1.split("\n")[1]
        assert input() == txt1.split("\n")[2]

    with replace_stdin(txt2):
        assert input() == str("a")
0

You can also mock the underlying stdin with the unittest mocking library. Suppose you have a function:

def get_user_confirmation() -> bool:
  """Returns True if the user entered "yes", otherwise False."""
  return input() == 'yes'

Then you can create the input buffer in the io.StringIO constructor and use that to mock stdin:

import io
import sys
import unittest
from unittest import mock

class MyTests(unittest.TestCase):

  def test_confirmation(self):
    user_input = 'yes\nfoo\n'
    with mock.patch.object(sys, 'stdin', io.StringIO(user_input)):
      self.assertTrue(get_user_confirmation())  # "yes"
      self.assertFalse(get_user_confirmation())  # "foo"

Note the trailing "\n"s to imitate the user pressing Enter after each line.

kris
  • 23,024
  • 10
  • 70
  • 79