REPL
Prompt toolkit can be used to build REPL interfaces. This section focuses in how to do it. If you want to build full screen applications instead go to this other article.
Testing⚑
Testing prompt_toolkit or any text-based user interface (TUI) with python is not well documented. Some of the main developers suggest mocking it while others use pexpect.
With the first approach you can test python functions and methods internally but it can lead you to the over mocking problem. The second will limit you to test functionality exposed through your program's command line, as it will spawn a process and interact it externally.
Given that the TUIs are entrypoints to your program, it makes sense to test them in end-to-end tests, so I'm going to follow the second option. On the other hand, you may want to test a small part of your TUI in a unit test, if you want to follow this other path, I'd start with monkeypatch, although I didn't have good results with it.
def test_method(monkeypatch):
monkeypatch.setattr('sys.stdin', io.StringIO('my input'))
Using pexpect⚑
This method is useful to test end to end functions as you need to all the program as a command line. You can't use it to tests python functions internally.
File: source.py
from prompt_toolkit import prompt
text = prompt("Give me some input: ")
with open("/tmp/tui.txt", "w") as f:
f.write(text)
File: test_source.py
import pexpect
def test_tui() -> None:
tui = pexpect.spawn("python source.py", timeout=5)
tui.expect("Give me .*")
tui.sendline("HI")
tui.expect_exact(pexpect.EOF)
with open("/tmp/tui.txt", "r") as f:
assert f.read() == "HI"
Where:
- The
tui.expect_exact(pexpect.EOF)
line is required so that the tests aren't run before the process has ended, otherwise the file might not exist yet. - The
timeout=5
is required in case that thepexpect
interaction is not well defined, so that the test is not hung forever.
Thank you Jairo Llopis for this solution.
I've deduced the solution from his #260 PR into copier, and the comments of #1243