Crafting Robust Test Helpers in Go
Writing effective unit tests is crucial for maintaining code quality. Test helpers are functions and types designed to simplify and streamline the process of writing tests, reducing boilerplate and improving readability. This challenge focuses on creating a suite of reusable test helpers in Go to aid in testing HTTP handlers.
Problem Description
You are tasked with creating a package named testhelpers that provides several utility functions to simplify testing HTTP handlers in Go. The package should include functions for:
-
CreateTestServer(handler http.Handler): This function should start a simple HTTP server usingnet/http/http.NewServer()(or a similar approach) and return the server's address (a string). The handler passed in should be registered as the default handler for all routes. The server should be stopped after the test completes (though you don't need to implement the stopping mechanism directly, just return the address so the test can use it). -
MakeRequest(t *testing.T, baseURL string, method, path string, body io.Reader, headers map[string]string) (*http.Response, error): This function should make an HTTP request to the specifiedbaseURLusing the givenmethod,path,body, andheaders. It should handle errors during the request and return thehttp.Responseand any error encountered. Thet *testing.Targument is for reporting errors within the test. -
AssertResponseStatus(t *testing.T, response *http.Response, expectedStatusCode int): This function should assert that theresponse's status code matches theexpectedStatusCode. If they don't match, it should report an error usingt.Errorf. -
ReadResponseBody(response *http.Response) (string, error): This function should read the entire body of theresponseas a string and return it, along with any error encountered during reading.
Examples
Example 1:
Input: handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
w.Write([]byte("Hello, World!"))
})
baseURL := "http://localhost:8080"
t *testing.T
method := "GET"
path := "/"
body := nil
headers := map[string]string{}
Output: serverAddress := "http://localhost:8080"
response, err := MakeRequest(t, serverAddress, method, path, body, headers)
AssertResponseStatus(t, response, http.StatusOK)
bodyString, err := ReadResponseBody(response)
// Assert bodyString == "Hello, World!"
Explanation: The test server is started, a GET request is made to the root path, the response status is asserted to be 200, and the response body is read and can be further asserted.
Example 2:
Input: handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusBadRequest)
w.Write([]byte("Bad Request"))
})
baseURL := "http://localhost:8080"
t *testing.T
method := "POST"
path := "/data"
body := bytes.NewBufferString("some data")
headers := map[string]string{"Content-Type": "application/json"}
Output: response, err := MakeRequest(t, baseURL, method, path, body, headers)
AssertResponseStatus(t, response, http.StatusBadRequest)
bodyString, err := ReadResponseBody(response)
// Assert bodyString == "Bad Request"
Explanation: A POST request is made with a body and headers. The response status is asserted to be 400, and the response body is read.
Example 3: (Edge Case - Request Failure)
Input: baseURL := "http://invalid-domain.com"
t *testing.T
method := "GET"
path := "/"
body := nil
headers := map[string]string{}
Output: response, err := MakeRequest(t, baseURL, method, path, body, headers)
// Expect err != nil (due to DNS resolution failure)
Explanation: The test attempts to make a request to an invalid domain. The MakeRequest function should return an error, which the test can then assert.
Constraints
- The
CreateTestServerfunction should return a string representing the server's address (e.g., "http://localhost:8080"). - The
MakeRequestfunction should use thenet/httppackage for making requests. - All functions should handle errors gracefully and return them when appropriate.
- The
AssertResponseStatusfunction should uset.Errorfto report assertion failures. - The
ReadResponseBodyfunction should read the entire response body into a string. - The test helpers should be designed to be reusable across different tests.
- The server created by
CreateTestServershould use a port that is unlikely to be in use (e.g., a random port). You don't need to implement the port allocation, just use a reasonable default.
Notes
- Consider using
testing.Tfor error reporting in your helper functions. - Think about how to handle different HTTP methods (GET, POST, PUT, DELETE, etc.).
- The
headersparameter inMakeRequestshould be amap[string]string. - Focus on creating clean, readable, and well-documented code.
- You don't need to implement complex features like request timeouts or connection pooling. The goal is to create a basic set of helpers for common testing scenarios.
- The
CreateTestServerfunction does not need to explicitly shut down the server. The test environment will handle that. Its responsibility is to return the address.