coffee-grinder
coffee-grinder

Reputation: 27600

Using unittest.mock to patch input() in Python 3

How do you use the @patch decorator to patch the built-in input() function?

For example, here's a function in question.py that I'd like to test, which contains a call to input():

def query_yes_no(question, default="yes"):
""" Adapted from http://stackoverflow.com/questions/3041986/python-command-line-yes-no-input """

    valid = {"yes": True, "y": True, "ye": True, "no": False, "n": False}
    if default is None:
        prompt = " [y/n] "
    elif default == "yes":
        prompt = " [Y/n] "
    elif default == "no":
        prompt = " [y/N] "
    else:
        raise ValueError("invalid default answer: '%s'" % default)

    while True:
        sys.stdout.write(question + prompt)
        choice = input().lower()

        if default is not None and choice == '':
            return valid[default]
        elif choice in valid:
            return valid[choice]
        else:
            sys.stdout.write("Please respond with 'yes' or 'no' "
                             "(or 'y' or 'n').\n")

Here's my test, which gives me the error "ImportError: No module named 'builtins'":

import unittest
from unittest.mock import patch

import question

class TestQueryYesNo(unittest.TestCase):

    @patch('__builtins__.input.return_value', 'y')
    def test_query_y(self):
        answer = question.query_yes_no("Blah?")
        self.assertTrue(answer)

Upvotes: 20

Views: 22261

Answers (5)

Wojtek
Wojtek

Reputation: 1

In my test I wanted to pass many different inputs to test match inside run, and It's works fine. Test passed.

import io
import unittest
from unittest.mock import patch
from TextApp import run


def input_args():
    yield "show"
    yield "switch ts1"
    yield "show"
    yield "exit"


class TestTextApp(unittest.TestCase):
    @patch("builtins.input", side_effect=input_args())
    def test_ShowSwitchTS1ShowExit(self, mock_input):
        expected = (
            "ToggleSwitch is: off\n"
            "ToggleSwitch is: off\n"
            "ToggleSwitch is: off\n"
            "LightBulb is: off\n"
            "ToggleSwitch is: on\n"
            "ToggleSwitch is: off\n"
            "ToggleSwitch is: off\n"
            "LightBulb is: on\n"
        )

        with patch("sys.stdout", new=io.StringIO()) as fake_out:
            run()
            result = fake_out.getvalue()
            self.assertEqual(expected, result)

Upvotes: 0

Eric Amell
Eric Amell

Reputation: 3

For Python 3.8 the accepted answer didn't work for me. It didn't like the positional parameter even though my code was actually utilizing it. What worked for me was simply:

@patch('builtins.input')

Not sure if I am doing something wrong, but here you are.

Upvotes: 0

falsetru
falsetru

Reputation: 369064

__builtin__ module is renamed to builtins in Python 3. Replace as follow:

@patch('builtins.input', lambda *args: 'y')

UPDATE

input has an optional parameter. updated the code to accept the optional parameter.

Upvotes: 36

Harry Moreno
Harry Moreno

Reputation: 11603

For Python 2.x:

@patch('__builtin__.input')

worked for me.

Upvotes: 1

tbc0
tbc0

Reputation: 1577

Or use Mock's return_value attribute. I couldn't get it to work as a decorator, but here's how to do it with a context manager:

>>> import unittest.mock
>>> def test_input_mocking():
...     with unittest.mock.patch('builtins.input', return_value='y'):
...         assert input() == 'y'
...
>>> def test_input_mocking():
...     with unittest.mock.patch('builtins.input', return_value='y'):
...         assert input() == 'y'
...         print('we got here, so the ad hoc test succeeded')
...
>>> test_input_mocking()
we got here, so the ad hoc test succeeded
>>>

Upvotes: 8

Related Questions