2

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

  • I'd focus on testing the **logic** of getting the user input (ie how `value` and `required` interact) rather than testing `input` itself, since at that point you'd be testing Python's built-in functions. – DeepSpace Feb 13 '22 at 11:33
  • @DeepSpace Isn't that just get_new_bookmark_data() calls get_user_input() and passes "Title" to it. The Title is then passed to input which displays the line to the user who then enters the title name which is returned to get_new_bookmark_data() and then it cycles to the next get_user_input call ... ? – impilcature Feb 13 '22 at 12:00
  • Does this answer your question? [python mocking raw input in unittests](https://stackoverflow.com/questions/21046717/python-mocking-raw-input-in-unittests). Note the answer for Python 3 (the accepted answer is for Python 2). – MrBean Bremen Feb 13 '22 at 14:26
  • No, it doesn’t. I’m using pytest not unittest. Specifically, my hurdle is that a method calls a method that then calls for user input. – impilcature Feb 13 '22 at 21:24
  • 1
    I'll clarify in an answer. – MrBean Bremen Feb 14 '22 at 05:45

1 Answers1

1

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"
    }
MrBean Bremen
  • 14,916
  • 3
  • 26
  • 46