英文:
How to unit test server sent event?
问题
你想在单元测试中读取服务器发送事件的响应,而且想尽量避免使用 time.Sleep。
目前,你是这样读取响应的 w http.ResponseRecorder
:
func (f *fixture) readResponse(t *testing.T) []byte {
t.Helper()
byteCh := make(chan []byte)
timeout := time.After(100 * time.Millisecond)
go func() {
for len(f.w.Body.Bytes()) == 0 {
}
byteCh <- f.w.Body.Bytes()
}()
select {
case b := <-byteCh:
return b
case <-timeout:
t.Error("no response received")
return nil
}
}
你等待事件被写入的方式是:
for len(f.w.Body.Bytes()) == 0 {
}
有没有其他的替代方法,而不使用 for 或者 time.Sleep
呢?
英文:
I want to read a response from a server sent event in a unit tests. I want to avoid time.Sleep at all cost.
Currently I read the response w http.ResponseRecorder
as such:
func (f *fixture) readResponse(t *testing.T) []byte {
t.Helper()
byteCh := make(chan []byte)
timeout := time.After(100 * time.Millisecond)
go func() {
for len(f.w.Body.Bytes()) == 0 {
}
byteCh <- f.w.Body.Bytes()
}()
select {
case b := <-byteCh:
return b
case <-timeout:
t.Error("no response received")
return nil
}
}
I wait for an event to be written as such:
for len(f.w.Body.Bytes()) == 0 {
}
What are the alternative without a for or a time.Sleep
?
答案1
得分: 1
我认为io.ReadAll
已经足够了。
func (f *fixture) readResponse(t *testing.T) []byte {
t.Helper()
byteCh := make(chan []byte)
timeout := time.After(100 * time.Millisecond)
go func() {
body, err := io.ReadAll(f.w.Body)
if err != nil {
// 错误处理
}
byteCh <- body
}()
select {
case b := <-byteCh:
return b
case <-timeout:
t.Error("未收到响应")
return nil
}
}
英文:
I suppose io.ReadAll
is sufficient.
func (f *fixture) readResponse(t *testing.T) []byte {
t.Helper()
byteCh := make(chan []byte)
timeout := time.After(100 * time.Millisecond)
go func() {
body, err := io.ReadAll(f.w.Body)
if err != nil {
// error handler
}
byteCh <- body
}()
select {
case b := <-byteCh:
return b
case <-timeout:
t.Error("no response received")
return nil
}
}
答案2
得分: 0
如何包装httptest.ResponseRecorder
并重写Write
和WriteString
方法呢?
package m
import (
"fmt"
"net/http"
"net/http/httptest"
"sync"
"testing"
"time"
)
type ServerEventRecorder struct {
httptest.ResponseRecorder
C chan []byte
}
func NewRecorder() *ServerEventRecorder {
return &ServerEventRecorder{
ResponseRecorder: *httptest.NewRecorder(),
C: make(chan []byte, 2),
}
}
func (rw *ServerEventRecorder) Write(buf []byte) (int, error) {
rw.C <- buf
return rw.ResponseRecorder.Write(buf)
}
func (rw *ServerEventRecorder) WriteString(str string) (int, error) {
rw.C <- []byte(str)
return rw.ResponseRecorder.WriteString(str)
}
func (rw *ServerEventRecorder) Close() {
close(rw.C)
}
func TestXxx(t *testing.T) {
handler := func(w http.ResponseWriter, r *http.Request) {
for i := 0; i < 5; i++ {
i := i
time.Sleep(100 * time.Millisecond)
fmt.Fprintf(w, "data: some text - %d", i)
}
}
req := httptest.NewRequest("GET", "http://example.com/foo", nil)
w := NewRecorder()
var wg sync.WaitGroup
wg.Add(1)
go func() {
for buf := range w.C {
t.Logf("%s\n", buf)
}
wg.Done()
}()
handler(w, req)
w.Close()
wg.Wait()
}
希望对你有帮助!
英文:
How about wrap httptest.ResponseRecorder
and overwrite the Write
and WriteString
method?
package m
import (
"fmt"
"net/http"
"net/http/httptest"
"sync"
"testing"
"time"
)
type ServerEventRecorder struct {
httptest.ResponseRecorder
C chan []byte
}
func NewRecorder() *ServerEventRecorder {
return &ServerEventRecorder{
ResponseRecorder: *httptest.NewRecorder(),
C: make(chan []byte, 2),
}
}
func (rw *ServerEventRecorder) Write(buf []byte) (int, error) {
rw.C <- buf
return rw.ResponseRecorder.Write(buf)
}
func (rw *ServerEventRecorder) WriteString(str string) (int, error) {
rw.C <- []byte(str)
return rw.ResponseRecorder.WriteString(str)
}
func (rw *ServerEventRecorder) Close() {
close(rw.C)
}
func TestXxx(t *testing.T) {
handler := func(w http.ResponseWriter, r *http.Request) {
for i := 0; i < 5; i++ {
i := i
time.Sleep(100 * time.Millisecond)
fmt.Fprintf(w, "data: some text - %d", i)
}
}
req := httptest.NewRequest("GET", "http://example.com/foo", nil)
w := NewRecorder()
var wg sync.WaitGroup
wg.Add(1)
go func() {
for buf := range w.C {
t.Logf("%s\n", buf)
}
wg.Done()
}()
handler(w, req)
w.Close()
wg.Wait()
}
答案3
得分: 0
我还在学习Go语言,但是像这样的代码会起作用吗?
type fixture struct {
w *httptest.ResponseRecorder
signal chan struct{}
mu sync.Mutex
}
func newFixture() *fixture {
return &fixture{
w: httptest.NewRecorder(),
signal: make(chan struct{}),
}
}
func (f *fixture) writeResponse(data []byte) {
f.mu.Lock()
f.w.Body = bytes.NewBuffer(data)
f.mu.Unlock()
// 信号通知响应已准备好
close(f.signal)
}
func (f *fixture) readResponse(t *testing.T) []byte {
t.Helper()
timeout := time.After(100 * time.Millisecond)
select {
case <-f.signal:
f.mu.Lock()
b := f.w.Body.Bytes()
f.mu.Unlock()
return b
case <-timeout:
t.Error("未收到响应")
return nil
}
}
在fixture结构体中添加了signal通道和一个互斥锁。因此,writeResponse函数设置响应体,并通过关闭signal通道来发出准备好的信号。readResponse函数在读取响应体之前等待信号。
我认为这样可以避免你的for循环和需要睡眠的问题。
英文:
I'm still learning go, but would something like this work?
type fixture struct {
w *httptest.ResponseRecorder
signal chan struct{}
mu sync.Mutex
}
func newFixture() *fixture {
return &fixture{
w: httptest.NewRecorder(),
signal: make(chan struct{}),
}
}
func (f *fixture) writeResponse(data []byte) {
f.mu.Lock()
f.w.Body = bytes.NewBuffer(data)
f.mu.Unlock()
// Signal that the response is ready
close(f.signal)
}
func (f *fixture) readResponse(t *testing.T) []byte {
t.Helper()
timeout := time.After(100 * time.Millisecond)
select {
case <-f.signal:
f.mu.Lock()
b := f.w.Body.Bytes()
f.mu.Unlock()
return b
case <-timeout:
t.Error("no response received")
return nil
}
}
The signal channel and a mutex added to the fixture struct. So the writeResponse sets the response body and it signals ready by closing the signal channel. The readResponse function waits for the signal before reading the response body.
I think this would avoid both your for loop and need to sleep.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论