0

This is a bug which I've attempted to reproduce with a minimal example, but so far unsuccessfully. The Go module is similar to the following:

.
├── go.mod
└── handler
    ├── handler.go
    ├── handler_test.go
    └── mock_handler.go

where handler.go is empty (contains package handler only), handler_test.go contains a Handler interface definition (which is the same as Go's http.Handler) and a placeholder test,

package handler

import (
    "net/http"
    "testing"
)

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

func TestMockHandler(t *testing.T) {
    mockHandler := MockHandler{}
    t.Log(mockHandler)
}

and mock_handler.go contains a MockHandler struct which implements the Handler interface and is generated using moq:

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

package handler

import (
    "net/http"
    "sync"
)

var (
    lockMockHandlerServeHTTP sync.RWMutex
)

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

// MockHandler is a mock implementation of Handler.
//
//     func TestSomethingThatUsesHandler(t *testing.T) {
//
//         // make and configure a mocked Handler
//         mockedHandler := &MockHandler{
//             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 MockHandler 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 *MockHandler) ServeHTTP(in1 http.ResponseWriter, in2 *http.Request) {
    if mock.ServeHTTPFunc == nil {
        panic("MockHandler.ServeHTTPFunc: method is nil but Handler.ServeHTTP was just called")
    }
    callInfo := struct {
        In1 http.ResponseWriter
        In2 *http.Request
    }{
        In1: in1,
        In2: in2,
    }
    lockMockHandlerServeHTTP.Lock()
    mock.calls.ServeHTTP = append(mock.calls.ServeHTTP, callInfo)
    lockMockHandlerServeHTTP.Unlock()
    mock.ServeHTTPFunc(in1, in2)
}

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

In order to generate mock_handler.go, I initially defined the Handler in handler.go and then, in the handler directory, ran the command

 moq -out mock_handler.go . Handler

I subsequently moved the Handler interface definition to handler_test.go since it is only intended to be used for tests.

In this simplified example, I'm able to run go test in package list mode in the root directory:

~/g/s/g/k/mockhandler> go test ./... -count=1
ok      github.com/kurtpeek/mockhandler/handler 0.448s

My 'actual' module has a similar structure, similar to the following:

.
├── cmd
│   └── root.go
├── images
├── main.go
└── vpp
    ├── ensure_license_test.go
    └── mock_handler.go

The Handler interface is defined in exactly the same way in ensure_license_test.go as in handler_test.go in the simplified module; ensure_license_test.go starts like this:

package vpp

import (
    "encoding/json"
    "io/ioutil"
    "net/http"
    "net/http/httptest"
    "net/url"
    "testing"

    "github.com/stretchr/testify/assert"
    "github.com/stretchr/testify/require"
)

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

type MockTestServer struct {
    TestServer  *httptest.Server
    MockHandler *MockHandler
}

mock_handler.go is also exactly the same as mock_handler.go in the simplified module (except for the package name).

Yet when I run go test ./... in the root directory of the 'actual' module, I get an undefined error for Handler:

~/g/s/g/f/vpp-client> go test ./... -count=1
# github.com/fleetsmith/vpp-client/vpp
vpp/mock_handler.go:17:7: undefined: Handler
ok      github.com/fleetsmith/vpp-client/vpp    0.128s

Strangely, when I run this from within the vpp package, it passes:

> go test ./... -count=1
ok      github.com/fleetsmith/vpp-client/vpp    0.601s

What could be the reason that go test is unable to define the definition of Handler when running it in package list mode from the root directory as in the first example?

Kurt Peek
  • 52,165
  • 91
  • 301
  • 526
  • Do not have your normal source files reference identifiers that were declared in a test file. If you need to reference `Handler` in the source file then simply declare it in a source file. Why? Because when you import a package you're only importing the source files, not the test files and if one of those source files references an identifier declared in a test file that is not included by the import you get the *undefined* compiler error. – mkopriva Jan 21 '20 at 08:05
  • That means that when you do `go test ./...` it is not the `vpp` package that is failing, it is one of the other packages, one that imports `vpp`, that is failing. – mkopriva Jan 21 '20 at 08:05

1 Answers1

0

It turns out that it was the cmd package for which tests were failing because it was unable to import the Handler interface from a test file in the vpp package. So I changed line 17 of mock_handler.go to use an http.Handler rather than a Handler:

var _ http.Handler = &MockHandler{}

Now the tests pass:

~/g/s/g/f/vpp-client> go test ./...
?       github.com/fleetsmith/vpp-client    [no test files]
?       github.com/fleetsmith/vpp-client/cmd    [no test files]
ok      github.com/fleetsmith/vpp-client/vpp    0.462s

I was also able to delete the definition of the Handler interface from ensure_license_test.go since I'm now using the definition from the http standard library directly.

The drawback of this approach is that it requires editing code that was auto-generated by moq, however, I wasn't able to figure out how to run moq to mock interfaces in Go's standard library and in any case this interface is unlikely to change.

Kurt Peek
  • 52,165
  • 91
  • 301
  • 526