How to test reverse proxy with martini in go

1.9k views Asked by At

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")
}
1

There are 1 answers

2
HectorJ On BEST ANSWER

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")
}