Uwe Schweinsberg
Uwe Schweinsberg

Reputation: 48

How to simulate two consecutive console inputs with pytest monkeypatch

The module "overwrite_file" (see code example) asks for input of a new filename if the first user input is answered with "n"

In my test setup I use two consecutive monkeypatch.setattr calls to simulate the input. The result is an endless loop if i use the order as follws:

monkeypatch.setattr('builtins.input', lambda overwrite: "n")
monkeypatch.setattr('builtins.input', lambda new_name: new_filename)

The second monkeypatch.setattr call is activated and 'new.pkl' is assigned to the variable overwrite.

If I change the order of the monkeypatch commands as so:

monkeypatch.setattr('builtins.input', lambda new_name: new_filename)
monkeypatch.setattr('builtins.input', lambda overwrite: "n")

I get an AssertionError as 'n' is assigned to the variable new_name and a file calld "n" is created.

How do I get the intended test functionality?

Interpreter: Python 3.8

from os.path import exists, join, dirname
import pickle
import pytest


def overwrite_file(filename):
    # loop until overwrite existing file or input of a file name which does not exist
    dump_file = False
    while not dump_file:
        if exists(filename):
            overwrite = input(f"overwrite {filename} (y/n): ")
            if overwrite in ["y", "Y"]:
                dump_file = True
            if overwrite in ["n", "N"]:
                new_name = input("new filename: ")
                filename = join(dirname(filename), new_name)
        else:
            dump_file = True

    return filename


@pytest.fixture()
def pickle_test_env(tmpdir_factory):
    a_dir = tmpdir_factory.mktemp('src_dir')
    a_file = a_dir.join('already_there.pkl')
    with open(a_file, "wb") as f:
        pickle.dump({"C": 27.1, "S": -8.2, "T": 29.7}, f)
    return a_dir


def test_new_filename_if_file_exists(pickle_test_env, monkeypatch):
    """ is overwrite_file returning a valid new filename if filename exists
    and should not be overwritten? """
    filename = 'already_there.pkl'
    new_filename = 'new.pkl'
    assert exists(join(pickle_test_env, filename))
    monkeypatch.setattr('builtins.input', lambda new_name: new_filename)
    monkeypatch.setattr('builtins.input', lambda overwrite: "n")
    assert overwrite_file(join(pickle_test_env, filename)) == join(pickle_test_env, new_filename)

Upvotes: 3

Views: 2710

Answers (1)

theY4Kman
theY4Kman

Reputation: 6105

The last monkeypatch will win out against all the others, so input(f"overwrite {filename} (y/n): ") is getting "n", and so is input("new filename: "). To provide the desired inputs in the correct order, we can monkeypatch a method that will cycle its responses

responses = iter(['n', new_filename])
monkeypatch.setattr('builtins.input', lambda msg: next(responses))

Note that responses is an iterator object — that is, calling next() on it will return the next item in the list. If input() is called more times than there are items in the list, StopIteration will be raised. An optional default value may be provided, avoiding the StopIteration exception, and allowing input() to be called forever and ever:

next(responses, '\n')

There may be a cleaner way to provide stdin to input(), but I'm at a loss at the moment.

Upvotes: 7

Related Questions