package httputil import ( "bytes" "encoding/gob" "testing" "time" ) type quote struct{ Author, Text string } // With no transport installed we are not on the client, so the fetch must not // happen and the callback must not fire — that is what leaves an SSR'd page in // its loading state (spinner/skeleton) rather than rendering a fetch error the // server had no way to avoid. func TestFetchWithoutTransportDoesNotCallBack(t *testing.T) { defer SetClientTransport(nil) SetClientTransport(nil) called := make(chan error, 1) FetchGob("/api/quotes", func(_ []quote, err error) { called <- err }) FetchJSON("/api/quotes", func(_ []quote, err error) { called <- err }) select { case err := <-called: t.Fatalf("callback ran with no transport installed (err=%v); SSR would render an error instead of the loading state", err) case <-time.After(50 * time.Millisecond): } } // The client path is unchanged: with a transport installed the body is fetched // and decoded into the same Go type the server encoded. func TestFetchGobWithTransport(t *testing.T) { defer SetClientTransport(nil) want := []quote{{Author: "Rob Pike", Text: "When in doubt, use brute force."}} var buf bytes.Buffer if err := gob.NewEncoder(&buf).Encode(want); err != nil { t.Fatal(err) } SetClientTransport(func(string) ([]byte, error) { return buf.Bytes(), nil }) got := make(chan []quote, 1) FetchGob("/api/quotes", func(qs []quote, err error) { if err != nil { t.Errorf("FetchGob: %v", err) } got <- qs }) select { case qs := <-got: if len(qs) != 1 || qs[0] != want[0] { t.Fatalf("decoded %v, want %v", qs, want) } case <-time.After(time.Second): t.Fatal("FetchGob callback never ran") } }