golang单元测试WebSocket客户端插件库wstest的使用

golang单元测试WebSocket客户端插件库wstest的使用

简介

wstest是一个用于单元测试WebSocket服务器的WebSocket客户端库。它允许您在不实际监听端口的情况下测试WebSocket处理程序。

标准库提供了httptest.ResponseRecorder来测试http.Handler,但当连接被HTTP升级器劫持时(如WebSocket连接)就无能为力了。wstest提供了NewDialer函数来测试升级到WebSocket会话的http.Handler

安装

go get -u github.com/posener/wstest

使用示例

基本用法

以下是一个完整的示例,展示如何使用wstest测试WebSocket处理程序:

package main

import (
	"net/http"
	"testing"

	"github.com/gorilla/websocket"
	"github.com/posener/wstest"
)

// 定义一个简单的WebSocket处理程序
type myHandler struct{}

func (h *myHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	// 升级HTTP连接到WebSocket
	upgrader := websocket.Upgrader{}
	conn, err := upgrader.Upgrade(w, r, nil)
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
	defer conn.Close()

	// 简单的消息处理循环
	for {
		_, msg, err := conn.ReadMessage()
		if err != nil {
			return
		}
		// 回显收到的消息
		err = conn.WriteMessage(websocket.TextMessage, msg)
		if err != nil {
			return
		}
	}
}

func TestHandler(t *testing.T) {
	// 创建我们的处理程序
	h := &myHandler{}
	
	// 使用wstest创建一个Dialer,而不是实际启动服务器
	d := wstest.NewDialer(h)
	
	// 连接到"服务器" - URL不重要,因为不会真正建立网络连接
	c, resp, err := d.Dial("ws://whatever/ws", nil)
	if err != nil {
		t.Fatal("Dial failed:", err)
	}
	defer c.Close()
	
	// 检查响应状态码
	if got, want := resp.StatusCode, http.StatusSwitchingProtocols; got != want {
		t.Errorf("resp.StatusCode = %q, want %q", got, want)
	}
	
	// 测试消息发送和接收
	testMsg := "hello, world"
	
	// 发送消息
	err = c.WriteJSON(testMsg)
	if err != nil {
		t.Fatal("WriteJSON failed:", err)
	}
	
	// 接收回显的消息
	var received string
	err = c.ReadJSON(&received)
	if err != nil {
		t.Fatal("ReadJSON failed:", err)
	}
	
	// 验证收到的消息
	if received != testMsg {
		t.Errorf("Received message = %q, want %q", received, testMsg)
	}
}

从httptest.Server迁移到wstest

以下是如何将使用httptest.Server的测试改为使用wstest.NewDialer的示例:

func TestHandler(t *testing.T) {
	var err error

	h := &myHandler{}
	// 旧方法:使用实际的HTTP测试服务器
	// s := httptest.NewServer(h)
	// defer s.Close()
	// d := websocket.Dialer{}
	
	// 新方法:使用wstest
	d := wstest.NewDialer(h)

	// 旧方法:连接到实际的测试服务器
	// c, resp, err := d.Dial("ws://" + s.Listener.Addr().String() + "/ws", nil)
	
	// 新方法:URL不重要
	c, resp, err := d.Dial("ws://" + "whatever" + "/ws", nil)
	if err != nil {
		t.Fatal(err)
	}
	
	if got, want := resp.StatusCode, http.StatusSwitchingProtocols; got != want {
		t.Errorf("resp.StatusCode = %q, want %q", got, want)
	}
	
	err = c.WriteJSON("test")
	if err != nil {
		t.Fatal(err)
	}
}

优势

  1. 不需要实际监听网络端口,测试更轻量
  2. 完全在内存中完成通信,测试速度更快
  3. 更容易模拟网络错误和异常情况
  4. 可以与标准测试工具链无缝集成

wstest是测试WebSocket处理程序的理想选择,特别是当您需要快速、可靠的单元测试时。


更多关于golang单元测试WebSocket客户端插件库wstest的使用的实战教程也可以访问 https://www.itying.com/category-94-b0.html

1 回复

更多关于golang单元测试WebSocket客户端插件库wstest的使用的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


Golang WebSocket 客户端测试库 wstest 使用指南

wstest 是一个用于测试 WebSocket 客户端的 Go 库,它可以帮助你模拟 WebSocket 服务器行为,方便进行单元测试。下面我将详细介绍如何使用 wstest 来测试 WebSocket 客户端。

安装 wstest

首先,使用 go get 安装 wstest:

go get github.com/gorilla/websocket
go get github.com/posener/wstest

基本使用方法

1. 最简单的测试用例

package main

import (
	"net/http"
	"net/http/httptest"
	"testing"
	"time"

	"github.com/gorilla/websocket"
	"github.com/posener/wstest"
	"github.com/stretchr/testify/assert"
)

func TestWebSocketClient(t *testing.T) {
	// 创建一个测试用的 WebSocket 服务器
	d := wstest.NewDialer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		// 升级为 WebSocket 连接
		conn, err := websocket.Upgrade(w, r, nil, 1024, 1024)
		if err != nil {
			t.Fatalf("升级连接失败: %v", err)
		}
		defer conn.Close()

		// 读取客户端消息
		_, msg, err := conn.ReadMessage()
		if err != nil {
			t.Fatalf("读取消息失败: %v", err)
		}

		// 回显消息
		if err := conn.WriteMessage(websocket.TextMessage, msg); err != nil {
			t.Fatalf("写入消息失败: %v", err)
		}
	}))

	// 客户端连接
	conn, resp, err := d.Dial("ws://example.org/ws", nil)
	if err != nil {
		t.Fatalf("连接失败: %v", err)
	}
	defer conn.Close()

	// 检查响应状态码
	assert.Equal(t, http.StatusSwitchingProtocols, resp.StatusCode)

	// 发送测试消息
	testMsg := "hello, world"
	if err := conn.WriteMessage(websocket.TextMessage, []byte(testMsg)); err != nil {
		t.Fatalf("发送消息失败: %v", err)
	}

	// 读取回显消息
	_, msg, err := conn.ReadMessage()
	if err != nil {
		t.Fatalf("接收消息失败: %v", err)
	}

	// 验证消息
	assert.Equal(t, testMsg, string(msg))
}

2. 更复杂的测试场景

func TestWebSocketWithAuth(t *testing.T) {
	// 创建带认证的测试服务器
	handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		// 检查认证头
		if r.Header.Get("Authorization") != "Bearer valid-token" {
			w.WriteHeader(http.StatusUnauthorized)
			return
		}

		conn, err := websocket.Upgrade(w, r, nil, 1024, 1024)
		if err != nil {
			t.Fatalf("升级连接失败: %v", err)
		}
		defer conn.Close()

		// 处理消息循环
		for {
			mt, msg, err := conn.ReadMessage()
			if err != nil {
				return
			}

			// 处理不同类型的消息
			switch mt {
			case websocket.TextMessage:
				conn.WriteMessage(websocket.TextMessage, []byte("echo: "+string(msg)))
			case websocket.BinaryMessage:
				conn.WriteMessage(websocket.BinaryMessage, msg)
			case websocket.PingMessage:
				conn.WriteMessage(websocket.PongMessage, nil)
			}
		}
	})

	d := wstest.NewDialer(handler)

	// 创建带认证头的请求
	headers := http.Header{}
	headers.Set("Authorization", "Bearer valid-token")

	// 连接服务器
	conn, _, err := d.Dial("ws://example.org/ws", headers)
	if err != nil {
		t.Fatalf("连接失败: %v", err)
	}
	defer conn.Close()

	// 测试文本消息
	textMsg := "test message"
	if err := conn.WriteMessage(websocket.TextMessage, []byte(textMsg)); err != nil {
		t.Fatalf("发送文本消息失败: %v", err)
	}

	_, resp, err := conn.ReadMessage()
	if err != nil {
		t.Fatalf("接收响应失败: %v", err)
	}
	assert.Equal(t, "echo: "+textMsg, string(resp))

	// 测试二进制消息
	binaryMsg := []byte{0x01, 0x02, 0x03}
	if err := conn.WriteMessage(websocket.BinaryMessage, binaryMsg); err != nil {
		t.Fatalf("发送二进制消息失败: %v", err)
	}

	_, respBinary, err := conn.ReadMessage()
	if err != nil {
		t.Fatalf("接收二进制响应失败: %v", err)
	}
	assert.Equal(t, binaryMsg, respBinary)
}

3. 测试连接关闭

func TestWebSocketClose(t *testing.T) {
	// 创建会主动关闭连接的服务器
	handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		conn, err := websocket.Upgrade(w, r, nil, 1024, 1024)
		if err != nil {
			t.Fatalf("升级连接失败: %v", err)
		}
		defer conn.Close()

		// 读取一条消息后关闭连接
		_, _, err = conn.ReadMessage()
		if err != nil {
			return
		}

		// 发送关闭帧
		conn.WriteMessage(websocket.CloseMessage, 
			websocket.FormatCloseMessage(websocket.CloseNormalClosure, "goodbye"))
	})

	d := wstest.NewDialer(handler)
	conn, _, err := d.Dial("ws://example.org/ws", nil)
	if err != nil {
		t.Fatalf("连接失败: %v", err)
	}
	defer conn.Close()

	// 发送消息触发服务器关闭
	if err := conn.WriteMessage(websocket.TextMessage, []byte("trigger close")); err != nil {
		t.Fatalf("发送消息失败: %v", err)
	}

	// 等待关闭
	_, _, err = conn.ReadMessage()
	assert.Error(t, err)
	assert.True(t, websocket.IsCloseError(err, websocket.CloseNormalClosure))
}

高级用法

1. 测试并发连接

func TestConcurrentWebSocketConnections(t *testing.T) {
	handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		conn, err := websocket.Upgrade(w, r, nil, 1024, 1024)
		if err != nil {
			t.Fatalf("升级连接失败: %v", err)
		}
		defer conn.Close()

		// 简单回显
		for {
			_, msg, err := conn.ReadMessage()
			if err != nil {
				return
			}
			conn.WriteMessage(websocket.TextMessage, msg)
		}
	})

	d := wstest.NewDialer(handler)

	// 测试并发连接
	var wg sync.WaitGroup
	for i := 0; i < 10; i++ {
		wg.Add(1)
		go func(i int) {
			defer wg.Done()

			conn, _, err := d.Dial("ws://example.org/ws", nil)
			if err != nil {
				t.Errorf("连接 %d 失败: %v", i, err)
				return
			}
			defer conn.Close()

			msg := fmt.Sprintf("message %d", i)
			if err := conn.WriteMessage(websocket.TextMessage, []byte(msg)); err != nil {
				t.Errorf("发送消息 %d 失败: %v", i, err)
				return
			}

			_, resp, err := conn.ReadMessage()
			if err != nil {
				t.Errorf("接收响应 %d 失败: %v", i, err)
				return
			}

			assert.Equal(t, msg, string(resp))
		}(i)
	}
	wg.Wait()
}

2. 测试超时场景

func TestWebSocketTimeout(t *testing.T) {
	// 创建慢速服务器
	handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		conn, err := websocket.Upgrade(w, r, nil, 1024, 1024)
		if err != nil {
			t.Fatalf("升级连接失败: %v", err)
		}
		defer conn.Close()

		// 慢速处理
		time.Sleep(2 * time.Second)
		conn.WriteMessage(websocket.TextMessage, []byte("response"))
	})

	d := wstest.NewDialer(handler)
	conn, _, err := d.Dial("ws://example.org/ws", nil)
	if err != nil {
		t.Fatalf("连接失败: %v", err)
	}
	defer conn.Close()

	// 设置读取超时
	conn.SetReadDeadline(time.Now().Add(1 * time.Second))

	// 发送请求
	if err := conn.WriteMessage(websocket.TextMessage, []byte("request")); err != nil {
		t.Fatalf("发送消息失败: %v", err)
	}

	// 读取响应应该会超时
	_, _, err = conn.ReadMessage()
	assert.Error(t, err)
	assert.True(t, os.IsTimeout(err))
}

总结

wstest 提供了一种简单的方式来测试 WebSocket 客户端代码,主要优点包括:

  1. 不需要实际启动 HTTP 服务器
  2. 可以完全控制服务器行为
  3. 支持测试各种 WebSocket 协议特性
  4. 易于集成到现有的测试框架中

通过合理使用 wstest,你可以为 WebSocket 客户端代码编写全面的单元测试,覆盖正常流程、错误处理和边界情况。

回到顶部