dowm41315 2015-04-09 19:26
浏览 51
已采纳

Golang和Martini:模拟测试示例

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 i.e. 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.
  • 写回答

1条回答 默认 最新

  • dongliao2011 2015-04-09 22:51
    关注

    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, e.g. 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.

    本回答被题主选为最佳回答 , 对您是否有帮助呢?
    评论

报告相同问题?

悬赏问题

  • ¥15 HFSS 中的 H 场图与 MATLAB 中绘制的 B1 场 部分对应不上
  • ¥15 如何在scanpy上做差异基因和通路富集?
  • ¥20 关于#硬件工程#的问题,请各位专家解答!
  • ¥15 关于#matlab#的问题:期望的系统闭环传递函数为G(s)=wn^2/s^2+2¢wn+wn^2阻尼系数¢=0.707,使系统具有较小的超调量
  • ¥15 FLUENT如何实现在堆积颗粒的上表面加载高斯热源
  • ¥30 截图中的mathematics程序转换成matlab
  • ¥15 动力学代码报错,维度不匹配
  • ¥15 Power query添加列问题
  • ¥50 Kubernetes&Fission&Eleasticsearch
  • ¥15 報錯:Person is not mapped,如何解決?