gogo-echogo-testing

Form variables not available in testing


I am a Go newbie. I have written an API server built on the Echo server, using the DeepMap OpenAPI generator and Postgres using pgxpool. It is working well enough and has been in use for a year, but that's not to say it's written properly :).

Testing the server has been using a shell script and a series of Curl calls, which has worked well enough, but I'm trying to update the testing to use Go's testing framework. I've got some basic tests going but anything requiring form values does not work--the handler function does not see any of the form values, so I'm guessing the request does not encapsulate them, but I don't understand why.

The following is the first part of the CreateNode() method which implements part of the generated API interface. I've left out the body; the part that's failing is what comes in on the context.

func (si *ServerImplementation) CreateNode(ctx echo.Context) error {
        vals, err := ctx.FormParams()
        info("In CreateNode() with FormParams %v", vals)
        ...

Here's the test function:

func TestCreateNode(t *testing.T) {
        // not the actual expected return
        expected := "Node created, hooray\n"

        // initialize database with current schema
        api := &ServerImplementation{}
        err := api.Init("host=localhost database=pgx_test user=postgres")
        if err != nil {
                t.Fatal(err)
        }

        // handle teardown in this deferred function
        t.Cleanup(func() {
                t.Log("Cleaning up API")
                api.Close()
        })

        // start up webserver
        e := echo.New()

        // this didn't work either
        //f := make(url.Values)
        //f.Set("name", "node1")
        //req := httptest.NewRequest(http.MethodPost, "/nodes/", strings.NewReader(f.Encode()))
        //req.Header.Add("Content-Type", "multipart/form-data")

        req := httptest.NewRequest(echo.POST, "/", strings.NewReader(`{"name":"node1"}`))
        req.Header.Set(echo.HeaderContentType, echo.MIMEApplicationJSON)
        rec := httptest.NewRecorder()
        ctx := e.NewContext(req, rec)

        if assert.NoError(t, api.CreateNode(ctx)) {
                assert.Equal(t, http.StatusOK, rec.Code)
                assert.Equal(t, expected, rec.Body.String())
        }
}

I won't bother with the full test output because everything fails when CreateNode() receives no values:

=== RUN   TestCreateNode
2023/08/26 15:09:43 INFO:    In CreateNode() with FormParams map[]
2023/08/26 15:09:43 INFO:    No name provided in CreateNode()

So far as I can tell I'm following similar examples pretty closely. I'm hoping this is enough detail but didn't want to overload the question with unnecessary supporting code.

The endpoint for nodes is /nodes and the base URL for the API is /api but neither are reflected here and from examples I've seen they aren't necessary. Echo's example always uses / as the endpoint.


Solution

  • Welp, I'm a ding-dong.

    I was clobbering together so many examples, trying to get something to work, and it was only once I tried the following, in the testing function:

            req.Header.Set("Testing", "Yes")
    

    And popped that out in CreateNode:

            info("Header: %v", ctx.Request().Header)
    

    Which gave me:

    2023/08/26 20:04:36 INFO:    Header: map[Content-Type:[application/x-www-form-urlencoded] Testing:[Yes]]
    

    That I saw that the request was making it through fine, and it was something in how I was forming the request.

    I checked the examples again and realized I was setting the form values according to one but setting the content-type from a different example. The following works:

            f := make(url.Values)
            f.Set("name", "node1")
            req := httptest.NewRequest(http.MethodPost, "/", strings.NewReader(f.Encode()))
            req.Header.Set(echo.HeaderContentType, echo.MIMEApplicationForm)
    

    And of course doing it via JSON is not going to work because that's not how CreateNode() parses the incoming information.

    This was just sloppy on my part!