Documentation
¶
Overview ¶
Package livetest makes live action handlers unit-testable.
Testing a handler used to mean a real HTTP server, a cookie exchange, and hand-parsed SSE frames, so handlers went untested. This package removes the transport: NewSession mints a session with a Recorder attached in place of a browser, Invoke runs a registered handler the way an incoming action would, and the Recorder holds every patch the handler produced.
srv := live.New()
defer srv.Close()
registerHandlers(srv) // the code under test
sess, rec := livetest.NewSession(t, srv)
if err := livetest.Invoke(t, sess, "add-todo", "todos", addDetail{Text: "milk"}); err != nil {
t.Fatal(err)
}
if _, ok := rec.Last("todos", "items"); !ok {
t.Error("add-todo did not patch the items prop")
}
Everything is synchronous: a Send inside a handler has reached the recorder by the time Invoke returns, so assertions need no waiting and no polling.
Example ¶
The shape of a handler test: register the code under test, invoke the action, assert on the patches — no HTTP server, no SSE parsing.
package main
import (
"fmt"
"testing"
"github.com/bmartel/alacris-go/live"
"github.com/bmartel/alacris-go/live/livetest"
)
func main() {
t := &testing.T{} // in a real test this is the *testing.T you were given
srv := live.New()
defer srv.Close()
live.On(srv, "add-todo", func(c *live.Ctx, d struct {
Text string `json:"text"`
}) error {
c.Session.Element("todos").Set("items", []string{d.Text})
return nil
})
sess, rec := livetest.NewSession(t, srv)
_ = livetest.Invoke(t, sess, "add-todo", "todos", map[string]string{"text": "milk"})
p, ok := rec.Last("todos", "items")
fmt.Println(ok, p.Key)
}
Output: true items
Index ¶
Examples ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func Invoke ¶
Invoke runs the handler registered for action, session-scoped or server-wide, with detail as the event's payload — the same code path an incoming action takes, minus the transport. element becomes Ctx.Element, which is what Ctx.Handle addresses; leave it empty when the handler does not use it.
detail may be nil, a json.RawMessage, or any value to marshal.
func RenderSession ¶
RenderSession mints a session the way the application's render handler would, with nothing attached yet — the state a page is in between render and the browser connecting. Register OnOpen on it, then Attach. The session is closed when the test ends.
Types ¶
type Recorder ¶
type Recorder struct {
// contains filtered or unexported fields
}
A Recorder holds the patches a session sent, standing in for the browser.
Reads are synchronous with the handlers that produced the patches: whatever a handler sent before returning is visible to the next Patches, Frames or Last call. Like a browser, the recorder must keep up — a session drops a subscriber more than 16 frames behind, so read (or batch) between bursts.
func Attach ¶
Attach connects a Recorder to the session in place of a browser: OnOpen runs, anything buffered since the render is recorded, and every later Send is visible to the recorder's reads. The recorder detaches when the test ends.
func NewSession ¶
NewSession mints a session on srv the way a page render would — cookie exchange included, on a synthetic request — and attaches a Recorder in place of a browser. The session is closed when the test ends.
To exercise OnOpen the way a real page does, keep the real order: mint with RenderSession, register OnOpen, then Attach.
func (*Recorder) Frames ¶
Frames returns every frame so far, in delivery order. Patches batched together arrive as one frame, so frame boundaries are the "lands in one paint" contract Batch makes.
func (*Recorder) Last ¶
Last returns the most recent patch for one element and key — the value the page would be showing — and whether any patch matched at all.