Gin: c.Stream does not work with httptest.ResponseRecorder

Created on 15 Mar 2019  Â·  5Comments  Â·  Source: gin-gonic/gin

Description

It's not possible to test endpoints that use gin.Context.Stream using httptest.ResponseRecorder. Here is a minimal reproducer:

package main

import (
    "github.com/gin-gonic/gin"
    "io"
    "net/http"
    "net/http/httptest"
    "testing"
)

func TestRecorder(t *testing.T) {
    router := gin.Default()
    router.GET("/stream", func(c *gin.Context) {
        c.Status(200)
        c.Stream(func(w io.Writer) bool {
            w.Write([]byte("Hello"))
            return false
        })
    })

    req, _ := http.NewRequest("GET", "/stream", nil)
    w := httptest.NewRecorder()
    router.ServeHTTP(w, req)
    if w.Body.String() != "Hello" {
        t.Fail()
    }
}

The stack trace:

interface conversion: *httptest.ResponseRecorder is not http.CloseNotifier: missing method CloseNotify
/usr/local/go/src/runtime/panic.go:513 (0x102d528)
    gopanic: reflectcall(nil, unsafe.Pointer(d.fn), deferArgs(d), uint32(d.siz), uint32(d.siz))
/usr/local/go/src/runtime/iface.go:85 (0x100a826)
    getitab: panic(&TypeAssertionError{concrete: typ, asserted: &inter.typ, missingMethod: m.init()})
/usr/local/go/src/runtime/iface.go:522 (0x100ba57)
    assertI2I: r.tab = getitab(inter, tab._type, false)
/[snip]/vendor/github.com/gin-gonic/gin/response_writer.go:108 (0x15572a2)
    (*responseWriter).CloseNotify: return w.ResponseWriter.(http.CloseNotifier).CloseNotify()
/[snip]/vendor/github.com/gin-gonic/gin/context.go:903 (0x154fbb1)
    (*Context).Stream: clientGone := w.CloseNotify()
/[snip]/bug_test.go:15 (0x156173d)
    TestRecorder.func1: c.Stream(func(w io.Writer) bool {
/[snip]/vendor/github.com/gin-gonic/gin/context.go:124 (0x154b549)
    (*Context).Next: c.handlers[c.index](c)
/[snip]/vendor/github.com/gin-gonic/gin/recovery.go:76 (0x155e439)
    RecoveryWithWriter.func1: c.Next()
/[snip]/vendor/github.com/gin-gonic/gin/context.go:124 (0x154b549)
    (*Context).Next: c.handlers[c.index](c)
/[snip]/vendor/github.com/gin-gonic/gin/logger.go:225 (0x155d790)
    LoggerWithConfig.func1: c.Next()
/[snip]/vendor/github.com/gin-gonic/gin/context.go:124 (0x154b549)
    (*Context).Next: c.handlers[c.index](c)
/[snip]/vendor/github.com/gin-gonic/gin/gin.go:388 (0x1554bca)
    (*Engine).handleHTTPRequest: c.Next()
/[snip]/vendor/github.com/gin-gonic/gin/gin.go:351 (0x1554411)
    (*Engine).ServeHTTP: engine.handleHTTPRequest(c)
/[snip]/bug_test.go:23 (0x1561561)
    TestRecorder: router.ServeHTTP(w, req)
/usr/local/go/src/testing/testing.go:827 (0x10f1fbe)
    tRunner: fn(t)
/usr/local/go/src/runtime/asm_amd64.s:1333 (0x105cf30)
    goexit: BYTE    $0x90   // NOP

My current workaround is to extend httptest.ResponseRecorder with the missing methods:

type GinResponseRecorder struct {
    http.ResponseWriter
}

func (GinResponseRecorder) CloseNotify() <-chan bool {
    return nil
}

func (GinResponseRecorder) Flush() {
}

...

router.ServeHTTP(GinResponseRecorder{w}, req)

I'm using gin revision 05b5c3ba7495fb3cd737cad8b35e62e0862ed1c2 and go version go1.11.2 darwin/amd64 on macOS Mojave 10.14.3.

Most helpful comment

@kszafran you are correct. If you want to write your own tests around streams you will need a Recorder that can handle CloseNotifier, Take a look at what what gin did! We can not "fix" httptest it just doesn't support this yet. But this is a clear example of how to test a CloseNotifier. Happy testing!

All 5 comments

interface conversion: *httptest.ResponseRecorder is not http.CloseNotifier: missing method CloseNotify why?

and I run it OK!

➜  ~ cat x.go
package main

import (
    "io"
    "github.com/gin-gonic/gin"
)

func main() {
    r := gin.Default()
    r.GET("/stream", func(c *gin.Context) {
        c.Status(200)
        c.Stream(func(w io.Writer) bool {
            w.Write([]byte("hello world"))
            return false
        })
    })
    r.Run(":9090")
}

Yes, it works on its own (if you curl it), but have you tried testing it with httptest.ResponseRecorder (like shown in the documentation: https://gin-gonic.com/docs/testing/)? httptest.ResponseRecorder does not implement http.CloseNotifier nor http.Flusher, both of which are required by gin.

@kszafran you are correct. If you want to write your own tests around streams you will need a Recorder that can handle CloseNotifier, Take a look at what what gin did! We can not "fix" httptest it just doesn't support this yet. But this is a clear example of how to test a CloseNotifier. Happy testing!

It's great to know that someone else has done it before (gin authors in this case), I did not know about it. I'll use a similar implementation to TestResponseRecorder then. Thanks!

@thinkerou Issue seems resolved.

Was this page helpful?
0 / 5 - 0 ratings