How can I simulate input to stdin for pyunit?

I'm trying to test a function that takes input from stdin, which I'm currently testing with something like this:

cat /usr/share/dict/words | ./spellchecker.py

In the name of test automation, is there any way that pyunit can fake input to raw_input()?


Solution 1:

The short answer is to monkey patch raw_input().

There are some good examples in the answer to How to display the redirected stdin in Python?

Here is a simple, trivial example using a lambda that throws away the prompt and returns what we want.

System Under Test

cat ./name_getter.py
#!/usr/bin/env python

class NameGetter(object):

    def get_name(self):
        self.name = raw_input('What is your name? ')

    def greet(self):
        print 'Hello, ', self.name, '!'

    def run(self):
        self.get_name()
        self.greet()

if __name__ == '__main__':
    ng = NameGetter()
    ng.run()

$ echo Derek | ./name_getter.py 
What is your name? Hello,  Derek !

Test case:

$ cat ./t_name_getter.py
#!/usr/bin/env python

import unittest
import name_getter

class TestNameGetter(unittest.TestCase):

    def test_get_alice(self):
        name_getter.raw_input = lambda _: 'Alice'
        ng = name_getter.NameGetter()
        ng.get_name()
        self.assertEquals(ng.name, 'Alice')

    def test_get_bob(self):
        name_getter.raw_input = lambda _: 'Bob'
        ng = name_getter.NameGetter()
        ng.get_name()
        self.assertEquals(ng.name, 'Bob')

if __name__ == '__main__':
    unittest.main()

$ ./t_name_getter.py -v
test_get_alice (__main__.TestNameGetter) ... ok
test_get_bob (__main__.TestNameGetter) ... ok

----------------------------------------------------------------------
Ran 2 tests in 0.000s

OK

Solution 2:

Update -- using unittest.mock.patch

Since python 3.3 there is new submodule for unittest called mock that does exactly what you need to do. For those using python 2.6 or above there is a backport of mock found here.

import unittest
from unittest.mock import patch

import module_under_test


class MyTestCase(unittest.TestCase):

    def setUp(self):
        # raw_input is untouched before test
        assert module_under_test.raw_input is __builtins__.raw_input

    def test_using_with(self):
        input_data = "123"
        expected = int(input_data)

        with patch.object(module_under_test, "raw_input", create=True, 
                return_value=expected):
            # create=True is needed as raw_input is not in the globals of 
            # module_under_test, but actually found in __builtins__ .
            actual = module_under_test.function()

        self.assertEqual(expected, actual)

    @patch.object(module_under_test, "raw_input", create=True)
    def test_using_decorator(self, raw_input):
        raw_input.return_value = input_data = "123"
        expected = int(input_data)

        actual = module_under_test.function()

        self.assertEqual(expected, actual)

    def tearDown(self):
        # raw input is restored after test
        assert module_under_test.raw_input is __builtins__.raw_input

if __name__ == "__main__":
    unittest.main()
# where module_under_test.function is:
def function():
    return int(raw_input("prompt> "))

Previous answer -- replacing sys.stdin

I think the sys module might be what you're looking for.

You can do something like

import sys

# save actual stdin in case we need it again later
stdin = sys.stdin

sys.stdin = open('simulatedInput.txt','r') 
# or whatever else you want to provide the input eg. StringIO

raw_input will now read from simulatedInput.txt whenever it is called. If the contents of simulatedInput was

hello
bob

then the first call to raw_input would return "hello", the second "bob" and third would throw an EOFError as there was no more text to read.

Solution 3:

Replace sys.stdin with an instance of StringIO, and load the StringIO instance with the data you want returned via sys.stdin. Also, sys.__stdin__ contains the original sys.stdin object, so restoring sys.stdin after your test is as simple as sys.stdin = sys.__stdin__.

Fudge is a great python mock module, with convenient decorators for doing patching like this for you, with automatic cleanup. You should check it out.

Solution 4:

You didn't describe what sort of code is in spellchecker.py, which gives me freedom to speculate.

Suppose it's something like this:

import sys

def check_stdin():
  # some code that uses sys.stdin

To improve testability of check_stdin function, I propose to refactor it like so:

def check_stdin():
  return check(sys.stdin)

def check(input_stream):
  # same as original code, but instead of
  # sys.stdin it is written it terms of input_stream.

Now most of your logic is in check function, and you can hand-craft whatever input you can imagine in order to test it properly, without any need to deal with stdin.

My 2 cents.