Your first test
Write a test of a small Bubble Tea program from an empty file, break it on purpose, and read what steep tells you.
In this tutorial you'll write a test of a small program, run it, make it fail, and fix it. It takes about ten minutes. You need Go and a directory to work in.
Create the module
mkdir brew && cd brew
go mod init example.com/brew
go get charm.land/bubbletea/v2 getsteep.dev/steep@latestWrite a program to test
Put this in main.go. It's a list of teas: the arrow keys move a cursor, and enter orders the selected one.
package main
import (
"fmt"
"strings"
tea "charm.land/bubbletea/v2"
)
type model struct {
teas []string
cursor int
ordered string
}
func newModel() model {
return model{teas: []string{"Sencha", "Matcha", "Oolong"}}
}
func (m model) Init() tea.Cmd { return nil }
func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
if key, ok := msg.(tea.KeyPressMsg); ok {
switch key.String() {
case "down":
m.cursor = min(m.cursor+1, len(m.teas)-1)
case "up":
m.cursor = max(m.cursor-1, 0)
case "enter":
m.ordered = m.teas[m.cursor]
case "q":
return m, tea.Quit
}
}
return m, nil
}
func (m model) View() tea.View {
var b strings.Builder
for i, name := range m.teas {
cursor := " "
if i == m.cursor {
cursor = "> "
}
b.WriteString(cursor + name + "\n")
}
if m.ordered != "" {
fmt.Fprintf(&b, "\nOrdered %s\n", m.ordered)
}
return tea.NewView(b.String())
}
func main() {
if _, err := tea.NewProgram(newModel()).Run(); err != nil {
panic(err)
}
}Write the test
Put this in main_test.go:
package main
import (
"testing"
"getsteep.dev/steep"
)
func TestOrder(t *testing.T) {
steep.NewHarness(t, newModel()).
Wait("Sencha").
KeyDown().
Key("enter").
Wait("Ordered Matcha")
}Each line does what a person would. NewHarness starts the program in an emulated terminal. Wait blocks until text is on the screen, because the program draws at its own pace. KeyDown and Key press keys.
You don't have to quit the program. The harness registers a test cleanup that does it.
Run it
$ go test ./...
ok example.com/brew 0.052sMake it fail
Change Wait("Ordered Matcha") to Wait("Ordered Oolong"), then run the test again. After two seconds, the default timeout, steep gives up and prints what was on the screen:
--- FAIL: TestOrder (2.01s)
main_test.go:14: Wait("Ordered Oolong"): reel: wait for contains ["Ordered Oolong"]: steep: timeout
last output:
Sencha
> Matcha
Oolong
Ordered MatchaYou can read that like a bug report: the test wanted Ordered Oolong, the screen said Ordered Matcha, and the cursor was on the wrong row.
Watch the failure
Run it again, recording failures as a GIF and keeping the files:
STEEP_EXPORT_MODE=failure go test -artifacts ./...steep prints the path of each file it wrote, under _artifacts. You didn't change the test to get this, and a passing test writes nothing. Open the .gif to watch the run, or drop the .cast onto the inspector to step through it.
This is what a failure looks like in a recording. It's the same kind of failure, from the larger program these docs use:

Fix it, and keep the screen
Change the test back, and add a snapshot of the final screen:
steep.NewHarness(t, newModel()).
Wait("Sencha").
KeyDown().
Key("enter").
Wait("Ordered Matcha").
AssertSnapshot()There's no snapshot to compare with yet, so the first run fails and tells you how to make one:
$ go test ./...
--- FAIL: TestOrder (0.05s)
main_test.go:15: snapshot "testdata/TestOrder.snap" does not exist; set UPDATE_SNAPSHOTS=true to create it
$ UPDATE_SNAPSHOTS=true go test ./...
ok example.com/brew 0.051s
$ go test ./...
ok example.com/brew 0.049sCommit testdata/TestOrder.snap with your code. From now on, the test fails if that screen changes, and the failure shows a diff.
What you've learned
- A harness runs your program, and its methods are the things a person would do.
Waitblocks until the screen shows something, and fails with what was there instead.- Any test can be recorded from the environment, without changing it.
- Snapshots catch changes you weren't testing for.
Next
- Add steep to a project you already have.
- Learn the difference between
Assert,Require, andWait. - Read how waiting and settling work, which is where most flaky tests come from.
Last updated on