Search code examples
unit-testinggointerfacemockinggo-interface

How to mock an http.Handler with Go's moq?


I'm interested in creating an httptest.Server which simply records the requests it is called with. To this end, I'd like to use the github.com/matryer/moq library.

To generate a mock for an http.Handler, I copied its definition in a package called client:

package client

import "net/http"

type Handler interface {
    ServeHTTP(http.ResponseWriter, *http.Request)
}

I then ran

moq -out handler_mock.go . Handler

inside the client directory, which produced the following handler_mock.go:

// Code generated by moq; DO NOT EDIT.
// github.com/matryer/moq

package client

import (
    "net/http"
    "sync"
)

var (
    lockHandlerMockServeHTTP sync.RWMutex
)

// Ensure, that HandlerMock does implement Handler.
// If this is not the case, regenerate this file with moq.
var _ Handler = &HandlerMock{}

// HandlerMock is a mock implementation of Handler.
//
//     func TestSomethingThatUsesHandler(t *testing.T) {
//
//         // make and configure a mocked Handler
//         mockedHandler := &HandlerMock{
//             ServeHTTPFunc: func(in1 http.ResponseWriter, in2 *http.Request)  {
//                 panic("mock out the ServeHTTP method")
//             },
//         }
//
//         // use mockedHandler in code that requires Handler
//         // and then make assertions.
//
//     }
type HandlerMock struct {
    // ServeHTTPFunc mocks the ServeHTTP method.
    ServeHTTPFunc func(in1 http.ResponseWriter, in2 *http.Request)

    // calls tracks calls to the methods.
    calls struct {
        // ServeHTTP holds details about calls to the ServeHTTP method.
        ServeHTTP []struct {
            // In1 is the in1 argument value.
            In1 http.ResponseWriter
            // In2 is the in2 argument value.
            In2 *http.Request
        }
    }
}

// ServeHTTP calls ServeHTTPFunc.
func (mock *HandlerMock) ServeHTTP(in1 http.ResponseWriter, in2 *http.Request) {
    if mock.ServeHTTPFunc == nil {
        panic("HandlerMock.ServeHTTPFunc: method is nil but Handler.ServeHTTP was just called")
    }
    callInfo := struct {
        In1 http.ResponseWriter
        In2 *http.Request
    }{
        In1: in1,
        In2: in2,
    }
    lockHandlerMockServeHTTP.Lock()
    mock.calls.ServeHTTP = append(mock.calls.ServeHTTP, callInfo)
    lockHandlerMockServeHTTP.Unlock()
    mock.ServeHTTPFunc(in1, in2)
}

// ServeHTTPCalls gets all the calls that were made to ServeHTTP.
// Check the length with:
//     len(mockedHandler.ServeHTTPCalls())
func (mock *HandlerMock) ServeHTTPCalls() []struct {
    In1 http.ResponseWriter
    In2 *http.Request
} {
    var calls []struct {
        In1 http.ResponseWriter
        In2 *http.Request
    }
    lockHandlerMockServeHTTP.RLock()
    calls = mock.calls.ServeHTTP
    lockHandlerMockServeHTTP.RUnlock()
    return calls
}

However, if I try to run the following client_test.go,

package client

import (
    "net/http"
    "net/http/httptest"
    "testing"
)

func TestHandlerMock(t *testing.T) {
    handlerMock := HandlerMock{
        ServeHTTPFunc: func(w http.ResponseWriter, r *http.Request) {},
    }

    ts := httptest.NewServer(handlerMock)
    defer ts.Close()
}

I get

# github.com/kurtpeek/mock-handler/client [github.com/kurtpeek/mock-handler/client.test]
/Users/kurt/go/src/github.com/kurtpeek/mock-handler/client/client_test.go:14:26: cannot use handlerMock (type HandlerMock) as type http.Handler in argument to httptest.NewServer:
    HandlerMock does not implement http.Handler (ServeHTTP method has pointer receiver)
FAIL    github.com/kurtpeek/mock-handler/client [build failed]
Error: Tests failed.

Indeed if I look at the actual definition of ServeHTTP (from https://github.com/golang/go/blob/c112289ee4141ebc31db50328c355b01278b987b/src/net/http/server.go#L2008-L2013), ServeHTTP() does not have a pointer receiver:

// The HandlerFunc type is an adapter to allow the use of
// ordinary functions as HTTP handlers. If f is a function
// with the appropriate signature, HandlerFunc(f) is a
// Handler that calls f.
type HandlerFunc func(ResponseWriter, *Request)

// ServeHTTP calls f(w, r).
func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) {
    f(w, r)
}

How can I fix this to make a test server with a mocked handler function?


Solution

  • The error message is saying that your HandlerMock doesn't implement the interface http.Handler. However, *HandlerMock does implement it:

    func (mock *HandlerMock) ServeHTTP(in1 http.ResponseWriter, in2 *http.Request) 
    

    Try change the statement ts := httptest.NewServer(handlerMock) to ts := httptest.NewServer(&handlerMock)