gin-gonic / gin

Gin is a HTTP web framework written in Go (Golang). It features a Martini-like API with much better performance -- up to 40 times faster. If you need smashing performance, get yourself some Gin.
https://gin-gonic.com/
MIT License
78.89k stars 8.02k forks source link

Export createTestContext as a test helper #323

Closed danielalves closed 7 years ago

danielalves commented 9 years ago

I'm trying to write tests for my controllers (using Gin, a file that groups route handlers), but I'm not being able to mock gin Contexts. Inside the context_test.go file, there's a method that does exactly that:

func createTestContext() (c *Context, w *httptest.ResponseRecorder, r *Engine) {
    w = httptest.NewRecorder()
    r = New()
    c = r.allocateContext()
    c.reset()
    c.writermem.reset(w)
    return
}

But it is not exported. I also cannot replicate it because allocateContext and Context's engine field are unexported too.

What do you think about creating a test-helpers module and exporting createTestContext?

JimmyPettersson85 commented 9 years ago

I've been meaning to ask this as well. My way of testing my middleware usually looks something like this

func TestFooMiddleware(t *testing.T) {
    gin.SetMode(gin.TestMode)
    router := gin.New()
    router.Use(FooMiddleware())  //only use the mw I want to test
    router.GET("/test", func(c *gin.Context) {
        c.String(200, "OK")
    }

    w := httptest.NewRecorder()
    r, _ := http.NewRequest("GET", "/test", body)
    router.ServeHTTP(w, r)

    //assertions here
}   

Is this the correct way of testing middleware in gin?

manucorporat commented 9 years ago

@slimmy I think your code is the correct way. You can not test how a middleware aborts if you are using a fake context.

manucorporat commented 9 years ago

@danielalves I will merge your PR but you should understand the limitations. createTestContext() was created to test the context itself.

You can not test if the context stops the execution (since there is not a handler chain associated). Like you should still use the code proposed by @slimmy if you have an authorisation middleware that validates the credentials.

manucorporat commented 9 years ago

https://github.com/gin-gonic/gin/blob/master/auth_test.go#L91-L126

danielalves commented 9 years ago

@manucorporat Hey! Ok, I understand the limitations, but it is just what I need. For example, many of my tests look like this:

func mockContext(params map[string]string) (*gin.Context, *httptest.ResponseRecorder) {

    gin.SetMode(gin.TestMode)

    context, responseRecorder, _ := gin.CreateTestContext()

    var paramsSlice []gin.Param
    for key, value := range params {
        paramsSlice = append(paramsSlice, gin.Param{
            Key:   key,
            Value: value,
        })
    }

    context.Params = paramsSlice

    return context, responseRecorder
}

func TestCustomerController_CardById(t *testing.T) {

    Convey("CardById", t, func() {

        Convey("Handles invalid parameters", func() {

            Convey("It returns NotFound for string customerId", func() {

                mockedContext, responseRecorder := mockContext(map[string]string{
                    "customerId": "some-crazy-thing",
                    "cardId":     kTEST_CARD_ID,
                })

                CardById(mockedContext)

                cardJson := responseRecorder.Body.String()
                So(cardJson, ShouldBeBlank)
                So(responseRecorder.Code, ShouldEqual, http.StatusNotFound)
            })
        })
    })
})

func CardById(c *gin.Context) {
    // Router handler code
    // ...
}

This is just a simple example, but I'm sure you can understand how I would implement tests for checking the validity of jsons and etc. (Oh, I'm trying goconvey for tests, so don't mind all the Conveys :smile: )

danielalves commented 9 years ago

@manucorporat Sorry to bother, but any updates on this? I just wanted to add that using router.ServeHTTP(w, req) to test route handlers may be the most correct way (as in the link you've sent above) since the request goes all the way through the gin stack, but isn't it a little overkill for simple unit tests? I mean, for acceptance tests, where you do want to test everything together, that makes perfect sense. But, for unit tests, wouldn't it be simpler as I've suggested on my last comment? Afterall, I want to test just the handler behavior, not its behavior with other middlewares and etc

alediaferia commented 9 years ago

+1 for this

Actually this would help make better unit tests. I agree using httptest.Recorder is the right way most of the times but sometimes it is useful testing if my custom middleware actually sets the right values through func (c *Context) Set(key string, value interface{}) without having to check status codes which may not make sense with that case.

davisford commented 9 years ago

@manucorporat can you suggest the best way to test middleware functions - i.e. I don't necessarily want to build up a whole http stack, just test a function where I pass in the context and manipulate the context, and then assert some state on the context after the function.

i'm trying to figure out from this thread here the most appropriate way to create a context without building up a whole stack.

pierrec commented 8 years ago

Hello,

I understand all the benefits from having this as an exported function, but I have just run into an annoying side effect: since the function CreateTestContext() imports "net/http/httptest", it also adds an entry in cli arguments (https://golang.org/src/net/http/httptest/server.go#L69), which messes up with my cli arguments in my program.

Thoughts?

yehezkielbs commented 8 years ago

Please consider the PR https://github.com/gin-gonic/gin/pull/707 It make CreateTestContext() public without importing net/http/httptest

xiaods commented 8 years ago

how about any udpate?

tinchogob commented 7 years ago

How about making Context an interface, so I could pass a mock implementation to test my middlewares?

appleboy commented 7 years ago

Hi All,

Fixed by https://github.com/gin-gonic/gin/pull/707

svperfecta commented 7 years ago

Thanks for this!