如何对服务器发送的事件进行单元测试?

huangapple go评论76阅读模式
英文:

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 &lt;- f.w.Body.Bytes()
	}()

	select {
	case b := &lt;-byteCh:
		return b
	case &lt;-timeout:
		t.Error(&quot;no response received&quot;)
		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 &lt;- body
    }()

    select {
    case b := &lt;-byteCh:
        return b
    case &lt;-timeout:
        t.Error(&quot;no response received&quot;)
        return nil
    }
}

答案2

得分: 0

如何包装httptest.ResponseRecorder并重写WriteWriteString方法呢?

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 (
	&quot;fmt&quot;
	&quot;net/http&quot;
	&quot;net/http/httptest&quot;
	&quot;sync&quot;
	&quot;testing&quot;
	&quot;time&quot;
)

type ServerEventRecorder struct {
	httptest.ResponseRecorder
	C chan []byte
}

func NewRecorder() *ServerEventRecorder {
	return &amp;ServerEventRecorder{
		ResponseRecorder: *httptest.NewRecorder(),
		C:                make(chan []byte, 2),
	}
}

func (rw *ServerEventRecorder) Write(buf []byte) (int, error) {
	rw.C &lt;- buf
	return rw.ResponseRecorder.Write(buf)
}

func (rw *ServerEventRecorder) WriteString(str string) (int, error) {
	rw.C &lt;- []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 &lt; 5; i++ {
			i := i
			time.Sleep(100 * time.Millisecond)
			fmt.Fprintf(w, &quot;data: some text - %d&quot;, i)
		}
	}

	req := httptest.NewRequest(&quot;GET&quot;, &quot;http://example.com/foo&quot;, nil)
	w := NewRecorder()

	var wg sync.WaitGroup
	wg.Add(1)
	go func() {
		for buf := range w.C {
			t.Logf(&quot;%s\n&quot;, 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 &amp;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 &lt;-f.signal:
		f.mu.Lock()
		b := f.w.Body.Bytes()
		f.mu.Unlock()
		return b
	case &lt;-timeout:
		t.Error(&quot;no response received&quot;)
		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.

huangapple
  • 本文由 发表于 2023年4月21日 14:31:54
  • 转载请务必保留本文链接:https://go.coder-hub.com/76070481.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定