Simulating stdin Inputs from User

I recently ran into a problem where I was trying to automate unit testing for a function that paused, mid-execution, and waited for a user to input some value.

For example

def dummy_fn():
name = input()
return('Hello, ', name)
dummy_fn()
Nick

('Hello, ', 'Nick')


Simulating a user input wound up being a non-trivial thing to figure out, so I figured it beared writing a note involving:

• The StringIO class
• Temporarily overwriting sys.stdin

StringIO

io.StringIO is used to convert your typical string into something that can be read as a stream of inputs, much like reading lines in a file.

For instance, imagine we’re trying to simulate loading a .csv

from io import StringIO
import csv

Here, the iterator is considering each unique character. Because every other “line” is “a comma that splits two empty strings” we get a bunch of nonsense.

for char in csv.reader('a,b,c,d,e'):
print(char)
['a']
['', '']
['b']
['', '']
['c']
['', '']
['d']
['', '']
['e']


Whereas using StringIO, we can tell python to read our input as one unified line.

f = csv.reader(StringIO('a,b,c,d,e'))
for char in f:
print(char)
['a', 'b', 'c', 'd', 'e']


Temporarily Overwriting sys.stdin

sys.stdin is the default that gets called when you find yourself writing something that that uses the standard input() function. It’s got this cryptic TextIOWrapper for a repr, but it essentially takes whatever a user submits to standard in by typing and hitting Enter.

import sys
sys.stdin
<_io.TextIOWrapper name='<stdin>' mode='r' encoding='cp1252'>


But if we inspect, this TextIOWrapper inherits from the same base class as StringIO

sys.stdin.__class__.__base__
_io._TextIOBase

StringIO.__base__
_io._TextIOBase


Meaning we can leverage the same underlying functionality if we spoof a function designed to call sys.stdin. In this case, input()

with StringIO('asdf') as f:
stdin = sys.stdin
sys.stdin = f
print("'" + input() + "' wasn't actually typed at the command line")
sys.stdin = stdin
 'asdf' wasn't actually typed at the command line


(Note: Because Jupyter Notebooks use a different stdin scheme, this, ironically, is just markdown. But running it in IPython or regular ol’ Python works just fine)

Putting it All Together

with StringIO('Nick') as f:
stdin = sys.stdin
sys.stdin = f
dummy_fn()
sys.stdin = stdin
('Hello', 'Nick')