How can you monkeypatch an input call from a nested function?

Viewed 159

I am a novice python coder on my greatest of days. I am in a class and using pytest to wrap my head around TDD. Some of the functions in this code (based off of Dane Hillard's Bark) calls a function that prompts the user for input. I need to automate the input of the nested function.

def get_user_input(label, required=True):
    value = input(f"{label}: ") or None
    while required and not value:
        value = input(f"{label}: ") or None
    return value

def get_new_bookmark_data():
    return {
        "title": get_user_input("Title"),
        "url": get_user_input("URL"),
        "notes": get_user_input("Notes", required=False),
    }

I can't even wrap my head around how I should deal with test_get_user_input() much less the bookmark.

Here are some of the things I have tried:

def test_get_user_input(monkeypatch):
    uInput = 'a'
    monkeypatch.setattr('sys.stdin', uInput)
    assert barky.get_user_input() == 'A'

I'm just not there yet. Here is my repo: https://github.com/impilcature/Green-CIDM6330

1 Answers

Mocking is done the same way in pytest as in unittest by using unittest.mock.patch, in your case by mocking the input function that lives in buildins. So you can just do:

from unittest.mock import patch

@patch("builtins.input")
def test_get_user_input(mocked):
    mocked.return_value = "a"
    assert get_user_input('foo') == 'a'

That mocks input regardless of where it is called, so it doesn't matter if you call it in the tested function or in a function called by the tested function.

To be more consistent with the pytest way, you can install the pytest plugin pytest-mock which provides the mocker fixture, which is a wrapper around unittest.mock:

def test_get_user_input(mocker):
    mocker.patch('builtins.input', return_value='a')
    assert get_user_input('foo') == 'a'

If you want to test more than one input, you can use side_effect to provide subsequent inputs (here using the mocker fixture):

def test_get_new_bookmark_data(mocker):
    mocker.patch('builtins.input',
                 side_effect=["Some title", "Some URL", "Some notes"])
    assert get_new_bookmark_data() == {
        "title": "Some title",
        "url": "Some URL",
        "notes": "Some notes"
    }
Related