dqc42632 2017-05-12 21:55
浏览 96
已采纳

在单元测试中模拟context.Done()

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?

  • 写回答

2条回答 默认 最新

  • dongzhan7253 2017-05-13 14:33
    关注

    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)
        }
    }
    
    本回答被题主选为最佳回答 , 对您是否有帮助呢?
    评论
查看更多回答(1条)

报告相同问题?

悬赏问题

  • ¥15 PADS Logic 原理图
  • ¥15 PADS Logic 图标
  • ¥15 电脑和power bi环境都是英文如何将日期层次结构转换成英文
  • ¥15 DruidDataSource一直closing
  • ¥20 气象站点数据求取中~
  • ¥15 如何获取APP内弹出的网址链接
  • ¥15 wifi 图标不见了 不知道怎么办 上不了网 变成小地球了
  • ¥50 STM32单片机传感器读取错误
  • ¥50 power BI 从Mysql服务器导入数据,但连接进去后显示表无数据
  • ¥15 (关键词-阻抗匹配,HFSS,RFID标签)