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

5

19

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 https://mcmap.net/q/125797/-apt-command-line-interface-like-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)
Deliciadelicious answered 10/8, 2013 at 11:16 Comment(0)
R
35

__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.

Rivet answered 10/8, 2013 at 11:24 Comment(3)
@IlanBiala, It works for me: ideone.com/kdAjEd (tested on Python 3.5.1, Ubuntu 16.04 beta, WIndows 7, simplified for brevity)Rivet
input takes one positional argument, so: @patch('builtins.input', lambda _ : 'y')Gaffe
@SeF, Thank you for the comment. input's argument is optional; so I used *args instead of _.Rivet
A
8

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
>>>
Arkwright answered 26/5, 2016 at 17:51 Comment(0)
C
1

For Python 2.x:

@patch('__builtin__.input')

worked for me.

Cosmetic answered 25/8, 2017 at 18:12 Comment(0)
M
0

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.

Micronesia answered 31/7, 2020 at 16:8 Comment(0)
C
0

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)
Coracoid answered 11/5, 2024 at 18:55 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.