1. Внедрить внешние зависимости в тестируемое устройство
Самая большая проблема, которую я вижу сейчас, заключается в том, что URL, который вы пересылаете, жестко задан в вашей функции. Это очень затрудняет юнит-тестирование. Таким образом, первым шагом было бы извлечь URL из функции. Не зная остальной части вашего кода, Handler
кажется хорошим местом для этого. Упрощенная:
type Handler struct {
backend *url.URL
}
func NewHandler() (*Handler, error) {
backend, err := url.Parse("http://test.com")
if err != nil {
return nil, err
}
return &Handler{backend}, nil
}
func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
reverseProxy := &httputil.ReverseProxy{
Director: func(r *http.Request) {
r.URL.Host = h.backend.Host
r.URL.Path = "/"
r.URL.Scheme = h.backend.Scheme
r.Host = url.Host
r.Header.Set("X-Forwarded-Host", r.Header.Get("Host"))
},
}
reverseProxy.ServeHTTP(w, r)
}
Обратите внимание, что я переименовал Forward
в ServeHTTP
, чтобы упростить этот пример.
2. Используйте httptest
для тестирования реального обработчика
Следующим шагом является базовый тест:
func TestHandler(t *testing.T) {
// 1. set-up a backend server
// 2. set-up a reverse proxy with the handler we are testing
// 3. call the reverse-proxy
// 4. check that the backend server received the correct header
}
Давайте начнем с заполнения простых частей:
// set-up a backend server
backendServer := httptest.NewServer(http.DefaultServeMux)
defer backendServer.Close()
backendURL, err := url.Parse(backendServer.URL)
if err != nil {
t.Fatal(err)
}
// set-up the reverse proxy
handler := &Handler{backend: backendURL} // <-- here we inject our own endpoint!
reverseProxy := httptest.NewServer(handler)
defer reverseProxy.Close()
reverseProxyURL, err := url.Parse(reverseProxy.URL)
if err != nil {
t.Fatal(err)
}
// call the reverse proxy
res, err := http.Get(reverseProxy.URL)
if err != nil {
t.Fatal(err)
}
// todo optional: assert properties of the response
_ = res
// check that the backend server received the correct header
// this comes next...
3. Сообщите результаты с тестового сервера на тест
Теперь нам нужен способ сообщить полученный заголовок основному тесту. Поскольку наши тестовые серверы могут использовать произвольные обработчики, давайте расширим настройку нашего внутреннего сервера.
var (
mu sync.Mutex
header string
)
// create a backend server that checks the incoming headers
backendServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
mu.Lock()
defer mu.Unlock()
header = r.Header.Get("X-Forwarded-Host")
w.WriteHeader(http.StatusOK)
}))
defer backendServer.Close()
Обратите внимание, как я использую мьютекс, потому что обработчик будет работать в другой подпрограмме. Вы также можете использовать канал.
На данный момент мы можем реализовать наше утверждение:
mu.Lock()
got := header
mu.Unlock()
// check that the header has been set
want := reverseProxyURL.Host
if got != want {
t.Errorf("GET %s gives header %s, got %s", reverseProxy.URL, want, got)
}
Обратите внимание, что это все равно не удастся, но на этот раз, потому что тестируемый вами код неверен :-) r.Header.Get("Host")
следует заменить на r.Host
.
Приложение: полный пример
package example
import (
"net/http"
"net/http/httptest"
"net/http/httputil"
"net/url"
"sync"
"testing"
)
type Handler struct {
backend *url.URL
}
func NewHandler() (*Handler, error) {
backend, err := url.Parse("http://test.com")
if err != nil {
return nil, err
}
return &Handler{backend}, nil
}
func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
reverseProxy := &httputil.ReverseProxy{
Director: func(r *http.Request) {
r.URL.Host = h.backend.Host
r.URL.Path = "/"
r.URL.Scheme = h.backend.Scheme
r.Header.Set("X-Forwarded-Host", r.Host)
r.Host = h.backend.Host
},
}
reverseProxy.ServeHTTP(w, r)
}
func TestHandler(t *testing.T) {
var (
mu sync.Mutex
header string
)
// create a backend server that checks the incoming headers
backendServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
mu.Lock()
defer mu.Unlock()
header = r.Header.Get("X-Forwarded-Host")
w.WriteHeader(http.StatusOK)
}))
defer backendServer.Close()
backendURL, err := url.Parse(backendServer.URL)
if err != nil {
t.Fatal(err)
}
// create a server for your reverse proxy
handler := &Handler{backend: backendURL}
reverseProxy := httptest.NewServer(handler)
defer reverseProxy.Close()
reverseProxyURL, err := url.Parse(reverseProxy.URL)
if err != nil {
t.Fatal(err)
}
// make a request to the reverse proxy
res, err := http.Get(reverseProxy.URL)
if err != nil {
t.Fatal(err)
}
// todo optional: assert properties of the response
_ = res
mu.Lock()
got := header
mu.Unlock()
// check that the header has been set
want := reverseProxyURL.Host
if got != want {
t.Errorf("GET %s gives header %s, got %s", reverseProxy.URL, want, got)
}
}