How to patch input() when testing Jupyter notebook cells with testbook?

jupyter-notebook, monkeypatching, python, testbook, testing

Solution

I found the solution by digging into testbook's code and learning more about patching:

Patching `ipykernel.kernelbase.Kernel.raw_input` is the way to go and correct

but: Provide return values from `input()` using the `side_effect` argument, not `mock_input.return_value`

Correct test code:

@testbook(path)
def test_name_checking(tb):
    with tb.patch(
        "ipykernel.kernelbase.Kernel.raw_input",
        side_effect=["   haRrY   ", "  pOtter "],
    ) as mock_input:
        tb.execute_cell("name_checking")
        assert tb.cell_output_text("name_checking") == "Your name is Harry Potter."

Problem

A project I'm involved in uses testbook to test code cells of Jupyter notebooks. Patching works fine — unless the code to be tested asks for user input with `input()`. I just can't figure out how to patch it correctly. Versions used: Python: 3.8.10, testbook: 0.4.2 Code to be tested in a Jupyter code cell, tagged as `name_checking`: ``` def fix(text): return text.strip().title() def check(text): return len(text) > 1 firstname = input("What's your first name?") lastname = input("What's your last name?") fixed_first = fix(firstname) fixed_last = fix(lastname) if check(fixed_first) and check(fixed_last): print(f"Your name is {fixed_first} {fixed_last}.") else: print("You entered an invalid name.") ``` Attempt 1: The test code patches `builtins.input` ``` @testbook(path) def test_name_checking1(tb): # execute cell tagged "name_checking" with tb.patch("builtins.input") as mock_input: mock_input.return_value = [" haRrY ", " pOtter "] result = tb.execute_cell("name_checking") assert tb.cell_output_text("name_checking") == "Harry" ``` This fails with: ``` E 8 E ----> 9 firstname = input("What's your first name?") E 10 lastname = input("What's your last name?") E 11 E E ~/proj/.venv/lib/python3.8/site-packages/ipykernel/kernelbase.py in raw_input(self, prompt) E 976 """ E 977 if not self._allow_stdin: E --> 978 raise StdinNotImplementedError( E 979 "raw_input was called, but this frontend does not support input requests." E 980 ) E E StdinNotImplementedError: raw_input was called, but this frontend does not support input requests. E StdinNotImplementedError: raw_input was called, but this frontend does not support input requests. ../.venv/lib/python3.8/site-packages/testbook/client.py:135: TestbookRuntimeError ``` Attempt 2: The test code patches `ipykernel.kernelbase.Kernel.raw_input` ``` @testbook(path) def test_name_checking2(tb): with tb.patch("ipykernel.kernelbase.Kernel.raw_input") as mock_input: mock_input.return_value = [" haRrY ", " pOtter "] result = tb.execute_cell("name_checking") firstname = tb.ref("firstname") assert tb.cell_output_text("name_checking") == "Harry" ``` This fails with: ``` tb = <testbook.client.TestbookNotebookClient object at 0x7f74aaca2af0> @testbook(path) def test_name_checking2(tb): # execute cell tagged "name_checking" with tb.patch("ipykernel.kernelbase.Kernel.raw_input") as mock_input: mock_input.return_value = [" haRrY ", " pOtter "] result = tb.execute_cell("name_checking") firstname = tb.ref("firstname") print(firstname) > assert tb.cell_output_text("name_checking") == "Harry" E AssertionError: assert 'You entered an invalid name.' == 'Harry' E - Harry E + You entered an invalid name. 03-functions/tests/test_name_checking.py:33: AssertionError ----------------------------------------------------------------------------------------------------------------- Captured stdout call ------------------------------------------------------------------------------------------------------------------ "<MagicMock name='raw_input()' id='139650606478576'>" ``` Does anyone have an idea what has to be patched or how to correctly patch `input()`?

Original source