Testing user interfaces is not always obvious. Here are a few tricks for testing prompt_toolkit applications.
PosixPipeInput and DummyOutput¶During the creation of a prompt_toolkit Application
, we can specify what input and output device to be used. By default, these are output objects that correspond with sys.stdin and sys.stdout. In unit tests however, we want to replace these.
For the input, we want a “pipe input”. This is an input device, in which we can programmatically send some input. It can be created with create_pipe_input()
, and that return either a PosixPipeInput
or a Win32PipeInput
depending on the platform.
For the output, we want a DummyOutput
. This is an output device that doesn’t render anything. We don’t want to render anything to sys.stdout in the unit tests.
Note
Typically, we don’t want to test the bytes that are written to sys.stdout, because these can change any time when the rendering algorithm changes, and are not so meaningful anyway. Instead, we want to test the return value from the Application
or test how data structures (like text buffers) change over time.
So we programmatically feed some input to the input pipe, have the key bindings process the input and then test what comes out of it.
In the following example we use a PromptSession
, but the same works for any Application
.
from prompt_toolkit.shortcuts import PromptSession from prompt_toolkit.input import create_pipe_input from prompt_toolkit.output import DummyOutput def test_prompt_session(): with create_pipe_input() as inp: inp.send_text("hello\n") session = PromptSession( input=inp, output=DummyOutput(), ) result = session.prompt() assert result == "hello"
In the above example, don’t forget to send the \n character to accept the prompt, otherwise the Application
will wait forever for some more input to receive.
AppSession
¶
Sometimes it’s not convenient to pass input or output objects to the Application
, and in some situations it’s not even possible at all. This happens when these parameters are not passed down the call stack, through all function calls.
An easy way to specify which input/output to use for all applications, is by creating an AppSession
with this input/output and running all code in that AppSession
. This way, we don’t need to inject it into every Application
or print_formatted_text()
call.
Here is an example where we use create_app_session()
:
from prompt_toolkit.application import create_app_session from prompt_toolkit.shortcuts import print_formatted_text from prompt_toolkit.output import DummyOutput def test_something(): with create_app_session(output=DummyOutput()): ... print_formatted_text('Hello world') ...Pytest fixtures¶
In order to get rid of the boilerplate of creating the input, the DummyOutput
, and the AppSession
, we create a single fixture that does it for every test. Something like this:
import pytest from prompt_toolkit.application import create_app_session from prompt_toolkit.input import create_pipe_input from prompt_toolkit.output import DummyOutput @pytest.fixture(autouse=True, scope="function") def mock_input(): with create_pipe_input() as pipe_input: with create_app_session(input=pipe_input, output=DummyOutput()): yield pipe_input
For compatibility with pytest’s capsys
fixture, we have to create a new AppSession
for every test. This can be done in an autouse fixture. Pytest replaces sys.stdout
with a new object in every test that uses capsys
and the following will ensure that the new AppSession
will each time refer to the latest output.
from prompt_toolkit.application import create_app_session @fixture(autouse=True, scope="function") def _pt_app_session() with create_app_session(): yieldType checking¶
Prompt_toolkit 3.0 is fully type annotated. This means that if a prompt_toolkit application is typed too, it can be verified with mypy. This is complementary to unit tests, but also great for testing for correctness.
RetroSearch is an open source project built by @garambo | Open a GitHub Issue
Search and Browse the WWW like it's 1997 | Search results from DuckDuckGo
HTML:
3.2
| Encoding:
UTF-8
| Version:
0.7.4