英文:
Mock context.Done() in unit test
问题
我有一个HTTP处理程序,在每个请求上设置了一个上下文截止时间:
func submitHandler(stream chan data) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
ctx, cancel := context.WithTimeout(r.Context(), 5*time.Second)
defer cancel()
// 读取请求体等操作
select {
case stream <- req:
w.WriteHeader(http.StatusNoContent)
case <-ctx.Done():
err := ctx.Err()
if err == context.DeadlineExceeded {
w.WriteHeader(http.StatusRequestTimeout)
}
log.Printf("context done: %v", err)
}
}
}
我可以轻松测试http.StatusNoContent
头部,但是我不确定如何测试<-ctx.Done()
在select语句中的情况。
在我的测试用例中,我构建了一个模拟的context.Context
并将其传递给我的模拟http.Request
上的req.WithContext()
方法,然而,返回的状态码总是http.StatusNoContent
,这让我相信在我的测试中select
语句总是进入第一个case。
type mockContext struct{}
func (ctx mockContext) Deadline() (deadline time.Time, ok bool) {
return deadline, ok
}
func (ctx mockContext) Done() <-chan struct{} {
ch := make(chan struct{})
close(ch)
return ch
}
func (ctx mockContext) Err() error {
return context.DeadlineExceeded
}
func (ctx mockContext) Value(key interface{}) interface{} {
return nil
}
func TestHandler(t *testing.T) {
stream := make(chan data, 1)
defer close(stream)
handler := submitHandler(stream)
req, err := http.NewRequest(http.MethodPost, "/submit", nil)
if err != nil {
t.Fatal(err)
}
req = req.WithContext(mockContext{})
rec := httptest.NewRecorder()
handler.ServeHTTP(rec, req)
if rec.Code != http.StatusRequestTimeout {
t.Errorf("expected status code: %d, got: %d", http.StatusRequestTimeout, rec.Code)
}
}
我该如何模拟上下文截止时间已过的情况?
英文:
I have a HTTP handler that sets a context deadline on each request:
func submitHandler(stream chan data) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
ctx, cancel := context.WithTimeout(r.Context(), 5*time.Second)
defer cancel()
// read request body, etc.
select {
case stream <- req:
w.WriteHeader(http.StatusNoContent)
case <-ctx.Done():
err := ctx.Err()
if err == context.DeadlineExceeded {
w.WriteHeader(http.StatusRequestTimeout)
}
log.Printf("context done: %v", err)
}
}
}
I am easily able to test the http.StatusNoContent
header, but I am unsure about how to test the <-ctx.Done()
case in the select statement.
In my test case I have built a mock context.Context
and passed it to the req.WithContext()
method on my mock http.Request
, however, the status code returned is always http.StatusNoContent
which leads me to believe the select
statement is always falling into the first case in my test.
type mockContext struct{}
func (ctx mockContext) Deadline() (deadline time.Time, ok bool) {
return deadline, ok
}
func (ctx mockContext) Done() <-chan struct{} {
ch := make(chan struct{})
close(ch)
return ch
}
func (ctx mockContext) Err() error {
return context.DeadlineExceeded
}
func (ctx mockContext) Value(key interface{}) interface{} {
return nil
}
func TestHandler(t *testing.T) {
stream := make(chan data, 1)
defer close(stream)
handler := submitHandler(stream)
req, err := http.NewRequest(http.MethodPost, "/submit", nil)
if err != nil {
t.Fatal(err)
}
req = req.WithContext(mockContext{})
rec := httptest.NewRecorder()
handler.ServeHTTP(rec, req)
if rec.Code != http.StatusRequestTimeout {
t.Errorf("expected status code: %d, got: %d", http.StatusRequestTimeout, rec.Code)
}
}
How could I mock the context deadline has exceeded?
答案1
得分: 9
经过多次尝试和错误,我找出了问题所在。我不是试图创建一个模拟的context.Context
,而是创建了一个带有过期截止时间的新的context.Context
,并立即调用返回的cancelFunc
。然后,我将其传递给req.WithContext()
,现在它完美地工作了!
func TestHandler(t *testing.T) {
stream := make(chan data, 1)
defer close(stream)
handler := submitHandler(stream)
req, err := http.NewRequest(http.MethodPost, "/submit", nil)
if err != nil {
t.Fatal(err)
}
stream <- data{}
ctx, cancel := context.WithDeadline(req.Context(), time.Now().Add(-7*time.Hour))
cancel()
req = req.WithContext(ctx)
rec := httptest.NewRecorder()
handler.ServeHTTP(rec, req)
if rec.Code != http.StatusRequestTimeout {
t.Errorf("expected status code: %d, got: %d", http.StatusRequestTimeout, rec.Code)
}
}
英文:
So, after much trial and error I figured out what I was doing wrong. Instead of trying to create a mock context.Context
, I created a new one with an expired deadline and immediately called the returned cancelFunc
. I then passed this to req.WithContext()
and now it works like a charm!
func TestHandler(t *testing.T) {
stream := make(chan data, 1)
defer close(stream)
handler := submitHandler(stream)
req, err := http.NewRequest(http.MethodPost, "/submit", nil)
if err != nil {
t.Fatal(err)
}
stream <- data{}
ctx, cancel := context.WithDeadline(req.Context(), time.Now().Add(-7*time.Hour))
cancel()
req = req.WithContext(ctx)
rec := httptest.NewRecorder()
handler.ServeHTTP(rec, req)
if rec.Code != http.StatusRequestTimeout {
t.Errorf("expected status code: %d, got: %d", http.StatusRequestTimeout, rec.Code)
}
}
答案2
得分: 1
你的mockContext
类型的Done
方法永远不会返回done,因为没有任何东西写入通道,所以你的goroutine会一直等待,直到关闭通道,从而触发Done状态。如果你希望它立即报告完成,可以尝试以下代码:
func (ctx mockContext) Done() <-chan struct{} {
ch := make(chan struct{})
close(ch)
return ch
}
英文:
Your mockContext
type's Done
method will never return done, because nothing ever writes to the channel, so your goroutine sits around forever before it closes the channel, thus triggering the Done state. If you want it to immediately report done, try this:
func (ctx mockContext) Done() <-chan struct{} {
ch := make(chan struct{})
close(ch)
return ch
}
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论