gofiber / fiber

⚡️ Express inspired web framework written in Go
https://gofiber.io
MIT License
33.12k stars 1.63k forks source link

🤗 Testing a gofiber proxy handler #1929

Open dominicfollett opened 2 years ago

dominicfollett commented 2 years ago

Question description Hi there, thank you for the awesome Gofiber package. I have a question about testing Gofiber as a proxy. Small caveat: I am somewhat new to Go and Gofiber.

I have implemented a small proxy service using Gofiber, and I used your tests here as reference for writing my own tests.

I'd like to know if this the recommend or correct approach to testing Gofiber as a proxy? Or if I should be mocking out the proxying somehow.

Handler Function

func BulkEventsHandler(c *fiber.Ctx) error {
    url := buildURL(cfg().Proxy.Forward.Events, c.Path())

    // Before we do the proxy save the body to a DB
    // in a goroutine so that it doesn't block
    go saveEvent(copyBody(c.Request().Body()))

    if err := proxy.Do(c, url); err != nil {
        logrus.Error("Proxy to Foo failed: ", err)
        return err
    }

    // Remove Server header from response
    c.Response().Header.Del(fiber.HeaderServer)
    return nil
}

TestFunction

func TestBulkEventsHandler(t *testing.T) {
    // Parallel signals that this test is to be run in parallel
    // with (and only with) other parallel tests.
    t.Parallel()

    // Create a new fiber app
    app := fiber.New()

    // Make sure the proxy middleware doesn't have problems with
    // self-signed certificates
    proxy.WithTlsConfig(&tls.Config{
        InsecureSkipVerify: true,
    })

    // Create a fake server for handling the proxied request
    _, addr := createProxyTestServer(
        func(c *fiber.Ctx) error {
            return c.SendString("forwarded")
        }, t, "/events/bulk/:id",
    )

    // Mock out the function that returns config - there must be a better way
    // Adjust the config so that we're forwarding requests to our new server
    cfg = func() config.Config {
        return config.Config{
            Proxy: config.Proxy{
                Forward: config.Forward{
                    Events: addr,
                },
            },
        }
    }

    // Set up the route, and specify the handler under test
    app.Post("/events/bulk/:id", BulkEventsHandler)

    // Create some dummy event data
    bodyReader := strings.NewReader(`{"user": "12124", "data": "blah"}`)

    // Test our proxy
    resp, err := app.Test(
        httptest.NewRequest(
            "POST",
            "/events/bulk/1234",
            bodyReader,
        ),
    )

    utils.AssertEqual(t, nil, err)
    utils.AssertEqual(t, fiber.StatusOK, resp.StatusCode)

    b, err := ioutil.ReadAll(resp.Body)
    utils.AssertEqual(t, nil, err)
    utils.AssertEqual(t, "forwarded", string(b))
}

Is the following necessary? Should I instead somehow mock proxy.Do?

Server that handles the proxied request

func createProxyTestServer(handler fiber.Handler, t *testing.T, path string) (*fiber.App, string) {
    // Helper marks the calling function as a test helper function.
    t.Helper()

    // Create a new fiber app that will act as the server
    target := fiber.New(fiber.Config{DisableStartupMessage: true})
    // Set up the route that we will expect to be proxied to us
    target.Post(path, handler)

    // Create a listener on a random unused port
    ln, err := net.Listen(fiber.NetworkTCP4, "127.0.0.1:0")
    utils.AssertEqual(t, nil, err)

    // Generate self-signed certs for this server
    serverTLSConf, _, err := getTLSConfigs()
    utils.AssertEqual(t, nil, err)

    // Update the listener with the certs
    ln = tls.NewListener(ln, serverTLSConf)

    // This has to be run in a goroutine otherwise it will block
    go func() {
        // set the app's listener
        utils.AssertEqual(t, nil, target.Listener(ln))
    }()

    // Give the app time to get ready - this isn't ideal
    time.Sleep(2 * time.Second)
    addr := ln.Addr().String()

    return target, addr
}

Additionally, I'd like to test that the saveEvent method in my handler gets called, but the only way I think I could do that is if I altered the call signature of the handler e.g. func(c * fiber.Ctx, db *DB) where I could pass in a mock instead. But I don't think that is possible. Any recommendation would be most welcome. Thank you.

welcome[bot] commented 2 years ago

Thanks for opening your first issue here! 🎉 Be sure to follow the issue template! If you need help or want to chat with us, join us on Discord https://gofiber.io/discord