2

I'm writing test code for martini app working as a reverse proxy in go, and want to test it using httptest.ResponseRecorder, but I got an error the following.

[martini] PANIC: interface conversion: *httptest.ResponseRecorder is not http.CloseNotifier: missing method CloseNotify

httptest.ResponseRecorder has no method CloseNotify()

How should I test it?

package main

import (
        "github.com/go-martini/martini"
        "github.com/stretchr/testify/assert"
        "net/http"
        "net/http/httptest"
        "net/http/httputil"
        "net/url"
        "testing"
)

func TestReverseProxy(t *testing.T) {
        // Mock backend
        backendResponse := "I am the backend"
        backend := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
                w.Write([]byte(backendResponse))
        }))
        defer backend.Close()
        backendURL, _ := url.Parse(backend.URL)

        // Frontend
        m := martini.Classic()
        m.Get("/", func(w http.ResponseWriter, r *http.Request) {
                proxy := httputil.NewSingleHostReverseProxy(backendURL)
                proxy.ServeHTTP(w, r)
        })

        // Testing
        req, _ := http.NewRequest("GET", "/", nil)
        res := httptest.NewRecorder()
        m.ServeHTTP(res, req)

        assert.Equal(t, 200, res.Code, "should be equal")
}
Takamario
  • 105
  • 2
  • 7

1 Answers1

6

First, please note that the martini framework is no longer maintained as said in their README.

Then, about your issue, it's because Martini does something that looks pretty bad to me: it takes an http.ResponseWriter and assumes it is also an http.CloseNotifier, while there is absolutely no guarantee of this. They should take a custom interface wrapping both of them, something like that:

type ResponseWriterCloseNotifier interface {
    http.ResponseWriter
    http.CloseNotifier
}

You can see in their source code that they had the same issue for their own tests, and used some workaround: https://github.com/go-martini/martini/commit/063dfcd8b0f64f4e2c97f0bc27fa422969baa23b#L13

Here is some working code made with it:

package main

import (
    "net/http"
    "net/http/httptest"
    "net/http/httputil"
    "net/url"
    "testing"

    "github.com/go-martini/martini"
    "github.com/stretchr/testify/assert"
)

type closeNotifyingRecorder struct {
    *httptest.ResponseRecorder
    closed chan bool
}

func newCloseNotifyingRecorder() *closeNotifyingRecorder {
    return &closeNotifyingRecorder{
        httptest.NewRecorder(),
        make(chan bool, 1),
    }
}

func (c *closeNotifyingRecorder) close() {
    c.closed <- true
}

func (c *closeNotifyingRecorder) CloseNotify() <-chan bool {
    return c.closed
}

func TestReverseProxy(t *testing.T) {
    // Mock backend
    backendResponse := "I am the backend"
    backend := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        w.Write([]byte(backendResponse))
    }))
    defer backend.Close()
    backendURL, _ := url.Parse(backend.URL)

    // Frontend
    m := martini.Classic()
    m.Get("/", func(w http.ResponseWriter, r *http.Request) {
        proxy := httputil.NewSingleHostReverseProxy(backendURL)
        proxy.ServeHTTP(w, r)
    })

    // Testing
    req, _ := http.NewRequest("GET", "/", nil)
    res := newCloseNotifyingRecorder()
    m.ServeHTTP(res, req)

    assert.Equal(t, 200, res.Code, "should be equal")
}
HectorJ
  • 5,814
  • 3
  • 34
  • 52
  • Thanks, I found this solution in other site, but got `panic: runtime error: invalid memory address or nil pointer dereference` in my machine... > no longer maintained Ah yes, I know that. I will replace with any other framework. – Takamario Nov 29 '15 at 03:23
  • It seems a problem of go1.5.1 darwin/amd64... No problem in Ubuntu 14.04. – Takamario Nov 29 '15 at 05:40