Skip to content

Testing ​

Package chanxtest runs connections in memory: no network, no ports, nothing to clean up. A test connects, sends frames and asserts on what comes back, in the time a unit test takes. It is the Go counterpart of chanx's WebsocketCommunicator and capture helpers.

A test client ​

go
func TestChat(t *testing.T) {
	engine := NewEngine(chanx.Config{})
	alice := chanxtest.Connect(t, engine, "/ws/chat/lobby")
	bob := chanxtest.Connect(t, engine, "/ws/chat/lobby")

	alice.Send(ChatMessage{Payload: ChatPayload{Message: "hi"}})
	bob.Expect(`{"action": "chat_notification", "payload": {"message": "hi"}}`)

	note, before := chanxtest.ReceiveUntilMessage[ChatNotification](alice)
	if note.Payload.Message != "hi" || len(before) != 0 {
		t.Fatalf("alice got %+v after %v", note, before)
	}
}

Connect returns once the connection is set up: authenticated, groups joined, OnConnect run. A broadcast sent right after reaches it.

MethodDoes
Send(m, chanxtest.Ref("1"), chanxtest.Topic("room:a"))sends a message, with envelope fields
SendText(s), SendJSON(v), SendBinary(b)sends a raw frame
Receive()the next frame; fails after chanxtest.Timeout (3s)
Expect(json)the next frame equals this JSON, key order ignored
ExpectNothing(d)no frame arrives within d
ReceiveUntil("done")frames up to and including that action (complete when none is given)
chanxtest.ReceiveUntilMessage[T](c)the first frame carrying T, decoded, and the frames before it
Request(m)sends with a fresh ref, returns the frames answering it
Subscribe(topic), Unsubscribe(topic), ReceiveTopic(topic)topic helpers that assert the confirmation
Auththe authentication frame, on a route that authenticates
Close(), WaitClosed()close, or wait for the server to, and get the code

A Frame has Action(), Ref(), Topic(), Payload() and Decode(&v).

Connect options ​

go
c := chanxtest.Connect(t, engine, "/ws/chat/lobby?token=t-ann",
	chanxtest.Header("Authorization", "Bearer t-ann"),
	chanxtest.Cookie("sessionid", "abc"),
	chanxtest.Value(UserKey{}, User{Name: "ann"}), // as middleware would set it
	chanxtest.ClientIP("203.0.113.7"),
)

chanxtest.Dial is Connect returning a rejection before the upgrade as an error, to test RejectBeforeUpgrade. chanxtest.SkipAuthentication() leaves the authentication frame for the test to read.

Strict mode ​

By default the client fails the test when the server sends an action no handler declares: not a reply type, not in Operations Replies, not in Emits. That is how a broadcast missing from the AsyncAPI document, and so from generated clients, shows up in a test rather than in production. chanxtest.Lenient() turns it off for one client.

Errors and requests ​

go
func TestValidation(t *testing.T) {
	c := chanxtest.Connect(t, NewEngine(chanx.Config{}), "/ws/chat/lobby")
	c.SendText(`{"action": "chat", "payload": {}}`)
	c.Expect(`{"action": "error", "payload": [{"type": "missing", "loc": ["chat", "payload", "message"], "msg": "Field required"}]}`)
	c.ExpectNothing(50 * time.Millisecond) // and nothing else
}
go
func TestRequest(t *testing.T) {
	c := chanxtest.Connect(t, NewEngine(chanx.Config{SendCompletion: true}), "/ws/chat/lobby")
	frames := c.Request(chanx.PingMessage{}) // a fresh ref; collects until complete
	if len(frames) != 1 || frames[0].Action() != "pong" {
		t.Fatalf("got %v", frames)
	}
}

Asserting on what was published ​

Sometimes the thing to check is what a handler published, not what a client received: an event for other services, a broadcast to a group no test client is in. Wrap the layer in a spy:

go
func TestChatIsAudited(t *testing.T) {
	spy := chanxtest.Spy(inmemory.New())
	engine := NewEngine(chanx.Config{Layers: layers.Set{"default": spy}})
	c := chanxtest.Connect(t, engine, "/ws/chat/lobby")

	c.Send(ChatMessage{Payload: ChatPayload{Message: "hi"}})
	c.ReceiveUntil("chat_notification")

	posted := chanxtest.BroadcastEvents[MessagePosted](spy)
	if len(posted) != 1 || posted[0].Group != "audit" || posted[0].Event.Payload.Message != "hi" {
		t.Fatalf("published %+v", posted)
	}
}
SpyPython chanx
chanxtest.Spy(layer)wraps a layer and records every send
chanxtest.Spy(layer, chanxtest.Suppress())records without delivering
chanxtest.BroadcastEvents[T](spy)capture_broadcast_events: events of type T, with their group or channel
chanxtest.TopicBroadcasts[Topic, Event](spy)capture_topic_broadcasts: with the topic and seq
spy.Events(), spy.GroupMessages(), spy.All(), spy.Reset()raw records

Checking a definition ​

chanx.Check(&ChatConsumer{}) reports what Handle would panic on: wrong signatures, duplicate actions. One line, no engine.

Testing an adapter ​

A framework adapter of your own proves itself with chanxtest/adaptertest, over a real TCP listener, the suite gin, echo and Fiber pass. The same goes for a channel layer and layers/layertest.

Next: Frameworks.

MIT licensed