简体   繁体   中英

Golang with Martini: Mock testing example

I have put together a piece of code which does a GET on my route. I would like to test this using mocking. I am a Go and a test noob, so any tips are greatly appreciated.

My Generate Routes.go generates the routes for the current URL. Snippet:

func (h *StateRoute) GenerateRoutes (router *martini.Router) *martini.Router {
    r := *router

    /**
     * Get all states
     * 
     */
    r.Get("/state",  func( enc app.Encoder,
            db abstract.MongoDB,
            reqContext abstract.RequestContext,
            res http.ResponseWriter,
            req *http.Request) (int, string) {

        states := []models.State{}

        searchQuery := bson.M{}

        var q *mgo.Query = db.GetDB().C("states").Find(searchQuery)
        query, currentPage, limit, total := abstract.Paginate(req, q)
        query.All(&states)

        str, err := enc.EncodeWithPagination(currentPage, limit, total, states)

        return http.StatusOK, app.WrapResponse(str, err)
    })
}

And this is being called in my server.go as such:

var configuration = app.LoadConfiguration(os.Getenv("MYENV"))

// Our Martini API Instance
var apiInstance *martini.Martini

func init() {

    apiInstance = martini.New()
    // Setup middleware
    apiInstance.Use(martini.Recovery())
    apiInstance.Use(martini.Logger())

    // Add the request context middleware to support contexual data availability
    reqContext := &app.LRSContext{ }
    reqContext.SetConfiguration(configuration)

    producer := app.ConfigProducer(reqContext)
    reqContext.SetProducer(producer)

    apiInstance.MapTo(reqContext, (*abstract.RequestContext)(nil))

    // Hook in the OAuth2 Authorization object, to be processed before all requests
    apiInstance.Use(app.VerifyAuthorization)

    // Connect to the DB and Inject the DB connection into Martini
    apiInstance.Use(app.MongoDBConnect(reqContext))

    // Add the ResponseEncoder to allow JSON encoding of our responses
    apiInstance.Use(app.ResponseEncoder)

    // Add Route handlers
    r := martini.NewRouter()

    stateRouter := routes.StateRoute{}

    stateRouter.GenerateRoutes(&r)

    // Add the built router as the martini action
    apiInstance.Action(r.Handle)
}

My doubts:

  1. How does the mocking work here, considering I am trying to inject the dependency?

  2. Where should I start the testing from ie should I mock up r.Get in the Generate Routes? Right now, I've done this but since I'm using Martini which handles all the routing and requests, I'm quote lost if what I've done is right?

state_test.go:

type mockedStateRoute struct {
    // How can I mock the stateRoute struct?
    mock.Mock
}
type mockedEncoder struct {
    mock.Mock
}
type mockedMongoDB struct {
    mock.Mock
}
type mockedReqContext struct{
    mock.Mock
}
type mockedRespWriter struct{
    mock.Mock
}
type mockedReq struct{
    mock.Mock
}

func (m *mockedStateRoute) testGetStatesRoute(m1 mockedEncoder,
                    m2 mockedMongoDB, m3 mockedReqContext,
                    m4 mockedReqContext, m5 mockedRespWriter,
                    m6 mockedReq) (string) {
                        args := m.Called(m1,m2,m3,m4,m5,m6)
                        fmt.Print("You just called /states/GET")
                        // 1 is just a test value I want to return
                    return 1, args.Error(1)
}

func TestSomething (t *testing.T) {
    testObj := new(mockedStateRoute)

    testObj.On("testGetStatesRoute", 123).Return(true,nil)

    // My target function that does something with mockedStateRoute
    // How can I call the GET function in GenerateRoutes(). Or should I, since martini is handling all my requests
}

Links I've referred to:

  1. /stretchr/testify/mock doc
  2. examples of 1.

For doing dependency injection, the thing to test needs to have some way to receive its dependencies. In your code the connection to mongodb is done in the initialization of the thing to test itself, what doesn't allow to inject something that looks like a mongo connection, while being a mock.

There are many ways of achieving it, but one of the simplest and most direct ways to do dependency injection, is to make the thing to test to receive the dependency when it's created, this way its context is the place where the specific implementation of the dependency is configured. Take a look to this example :

type DataStore interface {
    Get(k string) string
    Set(k, v string)
}

type MyInstance struct {
    *martini.Martini
}

func NewAppInstance(d DataStore) *MyInstance {
    ...
}

func main() {
   d := NewRedisDataStore("127.0.0.1", 6379)
   NewAppInstance(d).Run()
}

The instance needs an implementation of a Datastore to work, it doesn't have to know anything about its internals, the only thing that matters is that it implements the interface, with both methods, Get and Set . Indeed, as a general rule in unit testing, you only want to test your code, not your dependencies. In this example, it uses Redis in "production", but, in testing:

type MockedDataStore struct {
    mock.Mock
}

func (m *MockedDataStore) Get(k string) string {
    args := m.Called(k)
    return args.String(0)
}

func (m *MockedDataStore) Set(k, v string) {
    m.Called(k, v)
}

It's just something without any functionality beyond letting the framework check that it has been called. In the test itself you have to configure the expectations with things like:

d := new(MockedDataStore)
...
d.On("Set", "foo", "42").Return().Once()
...
d.On("Get", "foo").Return("42").Once()

And, of course, initialize the instance with the mocked thing, and test it:

d := new(MockedDataStore)
instance := NewAppInstance(d)
d.On("Get", "foo").Return("42").Once()
request, _ = http.NewRequest("GET", "/get/foo", nil)
response = httptest.NewRecorder()
instance.ServeHTTP(response, request)
d.AssertExpectations(t)

So, as a summary, being more specific with the answers to your questions:

  1. You need to make your instance to be able to be initialized with its dependencies, eg creating a method that receives the dependencies and returns the instance. Then mock the dependencies and from test use the mocks instead of the "real" ones.

  2. Use the method ServeHTTP that martini provides to generate responses to HTTP requests, and httptest.NewRecorder() to simulate the reception of the response. Of course, if your application have more complex functionality that is used apart of the HTTP interface, you can also test it as normal methods.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM