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.
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.
Most helpful comment
@kszafran you are correct. If you want to write your own tests around streams you will need a
Recorderthat can handleCloseNotifier, Take a look at what what gin did! We can not "fix"httptestit just doesn't support this yet. But this is a clear example of how to test aCloseNotifier. Happy testing!