charmbracelet/x

[teatest] Testing a model with goroutines

Closed this issue · 6 comments

Hi,

Inside my model I have a goroutine fired after the user chooses a file in a list. This goroutine uses Program.Send() to communicate with the program. The problem I'm facing is that i can fake this program and build my model, but there is no way to check the results since the program my goroutine is communicating with is different from that fired by teatest.NewTestModel().
I don't have a specific solution, but it would be nice if I could run NewTestModel more granulary, maybe some new TestOption to specify the program to run.

Thanks for the good work, teatest really improves the bubbletea apps testability.

hey, not sure I understand your problem, can you provide a minimal reproducible?

type Cli struct {
    p *tea.Program
    str string
}

func (c *Cli) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
   switch msg := msg.(type) {
	case tea.KeyMsg:
		switch {
                    case "enter":
                        go c.updFunc()
                        return c, nil
                    default:
                        return c, nil
                }
       case string:
           c.str = msg
}

func (c *Cli) updFunc() {
     c.p.Send("update")
}

It was something like that. The fact is after posting here I realized how to update my view without the need of Send() and that keeping the program inside my model was a shit implementation, but still, I think it would be good to have a way to manipulate the *tea.Program during the tests 'cause there are certainly other app using tea.Program.Send() out there and they might be untestable with teatest. Maybe a more "bootstrapped" setup where you could build your own tea.Program and just pass it to teatest. I'm probably just thinking out loud here.
If I could think of a more structured solution and you agree with my suggestion I could open a PR.

yeah generally I would say it's not a good idea to have a reference of the program in the model itself...

the use for program.Send was intended (if I'm remembering correctly) to have multiple programs talk to each other, e.g. on a wish server

In that case, you'll indeed need to tea.Program... maybe we can then make it public in teatest.TestModel, so you can get the program from one test model and add it it to another, so they can talk to each other 🤔

In that case, you'll indeed need to tea.Program... maybe we can then make it public in teatest.TestModel, so you can get the program from one test model and add it it to another, so they can talk to each other 🤔

My idea is to add a new function to teatest like this:

func BootstrapTestModel(tb testing.TB, m tea.Model, options ...TestOption) (*TestModel, *tea.Program) {
	tm := NewTestModel(tb, m, options...)
	return tm, tm.program
}

So it wont break the current usage of NewTestModel and opens the possibility to access the program. What do you think? (The function name is totally open for suggestions)

it's not actually needed I think.

The only thing from tea.Program you should be using outside it is the Send method, so, instead of using the *tea.Program directly in your model, you can use a interface{ Send(tea.Msg) } and pass the teatest.TestModel directly

see #39 for example

closing as #39 shows how to do it.