How to test that a request fails with an invalid content type? - unit-testing

I'm using Chi with the builtin AllowContentType
middleware. Given this sample
package main
import (
"net/http"
"github.com/go-chi/chi/v5"
"github.com/go-chi/chi/v5/middleware"
)
func main() {
http.ListenAndServe(":3000", GetRouter())
}
func GetRouter() *chi.Mux {
apiRouter := chi.NewRouter()
apiRouter.Use(middleware.AllowContentType("application/json"))
apiRouter.Post("/my-route", func(responseWriter http.ResponseWriter, request *http.Request) {
responseWriter.WriteHeader(http.StatusCreated)
})
return apiRouter
}
I want to write a test to check if a route responds with a 415 if the content type is not application/json, I tried
package main
import (
"net/http"
"net/http/httptest"
"testing"
)
func TestGetRouter(testing *testing.T) {
router := GetRouter()
responseRecorder := httptest.NewRecorder()
request, _ := http.NewRequest(http.MethodPost, "/my-route", nil)
request.Header.Set("Content-Type", "text/xml")
router.ServeHTTP(responseRecorder, request)
if responseRecorder.Code != http.StatusUnsupportedMediaType {
testing.Errorf("Expected statuscode %d but got %d", http.StatusUnsupportedMediaType, responseRecorder.Code)
}
}
Unfortunately the test fails with
Expected statuscode 415 but got 201
so it seems the middleware passes and the route handler sends back the success code. How can I fix the test to ensure the middleware rejects the request with a 415?

It seems I have to pass in a non empty request body ( source code ), e.g.
requestBody := bytes.NewReader(make([]byte, 10, 10))
then I can setup the request like so
request, _ := http.NewRequest(http.MethodPost, "/my-route", requestBody)

Related

How to pass path param to httptest request

I am writing the unit test case for my http APIs, i need to pass the path param to the API endpoint
GetProduct(w http.ResponseWriter, r *http.Request) {
uuidString := chi.URLParam(r, "uuid")
uuid1, err := uuid.FromString(uuidString)
if err != nil {
w.WriteHeader(http.StatusBadRequest)
_, _ = w.Write([]byte(err.Error()))
return
}
}
I need to test this method and for that i need to pass a valid uuid to r http.Request, please suggest how can i do that, I tried a few options from my test class like
req.URL.Query().Set("uuid", "valid_uuid")
But it did not work. How can I test this method by passing a valid uuid to request?
Let me present my usual solution with gorilla package.
handler.go file
package httpunittest
import (
"net/http"
"github.com/gorilla/mux"
)
func GetProduct(w http.ResponseWriter, r *http.Request) {
params := mux.Vars(r)
uuidString, isFound := params["uuid"]
if !isFound {
w.WriteHeader(http.StatusBadRequest)
return
}
w.Write([]byte(uuidString))
}
Here, you use the function Vars to fetch all of the URL parameters present within the http.Request. Then, you've to look for the uuid key and do your business logic with it.
handler_test.go file
package httpunittest
import (
"net/http"
"net/http/httptest"
"testing"
"github.com/gorilla/mux"
"github.com/stretchr/testify/assert"
)
func TestGetProduct(t *testing.T) {
t.Run("WithUUID", func(t *testing.T) {
r := httptest.NewRequest(http.MethodGet, "/products/1", nil) // note that this URL is useless
r = mux.SetURLVars(r, map[string]string{"uuid": "1"})
w := httptest.NewRecorder()
GetProduct(w, r)
assert.Equal(t, http.StatusOK, w.Result().StatusCode)
})
t.Run("Without_UUID", func(t *testing.T) {
r := httptest.NewRequest(http.MethodGet, "/products", nil) // note that this URL is useless
w := httptest.NewRecorder()
GetProduct(w, r)
assert.Equal(t, http.StatusBadRequest, w.Result().StatusCode)
})
}
First, I used the functions provided by the httptest package of the Go Standard Library that fits well for unit testing our HTTP handlers.
Then, I used the function SetUrlVars provided by the gorilla package that allows us to set the URL parameters of an http.Request.
Thanks to this you should be able to achieve what you need!

How to test a handler in Gin which depends on external file?

I have a simple Gin server with one of the routes called /metadata.
What the handler does is it reads a file from the system, say /etc/myapp/metadata.json and returns the JSON in the response.
But when the file is not found, handler is configured to return following error.
500: metadata.json does not exists or not readable
On my system, which has the metadata.json file, the test passes. Here is the test function I am using:
package handlers_test
import (
"net/http"
"net/http/httptest"
"testing"
"myapp/routes"
"github.com/stretchr/testify/assert"
)
func TestMetadataRoute(t *testing.T) {
router := routes.SetupRouter()
w := httptest.NewRecorder()
req, _ := http.NewRequest("GET", "/metadata", nil)
router.ServeHTTP(w, req)
assert.NotNil(t, w.Body)
assert.Equal(t, 200, w.Code)
assert.Contains(t, w.Body.String(), "field1")
assert.Contains(t, w.Body.String(), "field2")
assert.Contains(t, w.Body.String(), "field3")
assert.Contains(t, w.Body.String(), "field4")
}
But on CI environment, the test would fail because it won't find metadata.json. And would return the configured error.
What can be done?
I have this handler:
func GetMetadata(c *gin.Context) {
// read the info
content, err := ioutil.ReadFile("/etc/myapp/metadata.json")
if err != nil {
c.JSON(http.StatusInternalServerError,
gin.H{"error": "metadata.json does not exists or not readable"})
return
}
// deserialize to json
var metadata models.Metadata
err = json.Unmarshal(content, &metadata)
if err != nil {
c.JSON(http.StatusInternalServerError,
gin.H{"error": "unable to parse metadata.json"})
return
}
c.JSON(http.StatusOK, metadata)
}
What Volker is suggesting is to use a package-level unexported variable. You give it a fixed default value, corresponding to the path you need in production, and then simply overwrite that variable in your unit test.
handler code:
var metadataFilePath = "/etc/myapp/metadata.json"
func GetMetadata(c *gin.Context) {
// read the info
content, err := ioutil.ReadFile(metadataFilePath)
// ... rest of code
}
test code:
func TestMetadataRoute(t *testing.T) {
metadataFilePath = "testdata/metadata_test.json"
// ... rest of code
}
This is a super-simple solution. There are ways to improve on this, but all are variations of how to inject any variable in a Gin handler. For simple request-scoped configuration, what I usually do is to inject the variable into the Gin context. This requires slightly refactoring some of your code:
router setup code with middleware for production
func SetupRouter() {
r := gin.New()
r.GET("/metadata", MetadataPathMiddleware("/etc/myapp/metadata.json"), GetMetadata)
// ... rest of code
}
func MetadataPathMiddleware(path string) gin.HandlerFunc {
return func(c *gin.Context) {
c.Set("_mdpath", path)
}
}
handler code extracting the path from context:
func GetMetadata(c *gin.Context) {
metadataFilePath := c.GetString("_mdpath")
content, err := ioutil.ReadFile(metadataFilePath)
// ... rest of code
}
test code which you should refactor to test the handler only (more details: How to unit test a Go Gin handler function?):
func TestMetadataRoute(t *testing.T) {
// create Gin test context
w := httptest.NewRecorder()
c, _ := gin.CreateTestContext(w)
// inject test value into context
c.Set("_mdpath", "testdata/metadata_test.json")
// just test handler, the passed context holds the test value
GetMetadata(c)
// ... assert
}
Note: setting context values with string keys is somewhat discouraged, however the Gin context accepts only string keys.

how to inject an url to httptest.server in golang?

For sentence
resp, err := client.Get(fmt.Sprintf("https://www.xxxxx/day?time=%s", time))
If I want to mock a response to this client.Get() in unit test, I should use httptest.server, but how can I bind the url (https://www.xxxxx/day?time=%s) to the url of httptest.server? so that when I call client.Get() it can return the response I set before.
For some reason I cannot mock a client here.
You don't, usually. You take the base URL from the server and give it to the client:
package main
import (
"fmt"
"net/http"
"net/http/httptest"
"testing"
"time"
)
func TestClient(t *testing.T) {
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// Verify request, send mock response, etc.
}))
defer server.Close()
var client *http.Client
var time time.Time
baseURL := server.URL // Something like "http://127.0.0.1:53791"
resp, err := client.Get(fmt.Sprintf(baseURL+"/day?time=%s", time))
if err != nil {
t.Fatal(err)
}
// Verify response body if applicable
resp.Body.Close()
}
Like this
func NewTestServerWithURL(URL string, handler http.Handler) (*httptest.Server, error) {
ts := httptest.NewUnstartedServer(handler)
if URL != "" {
l, err := net.Listen("tcp", URL)
if err != nil {
return nil, err
}
ts.Listener.Close()
ts.Listener = l
}
ts.Start()
return ts, nil
}
The http.Client is a struct not an interface which makes mocking it difficult as you have seen. An alternative way of mocking it is passing in the external dependencies that a routine needs, so instead of directly using client.Get, you use clientGet - which is a function pointer that was handed into the routine.
From the unit test you can then create :
mockClientGet(c *http.client, url string) (resp *http.Response, err error) {
// add the test code to return what you want it to.
}
Then in your main code use:
resp, err := clientGet(client, fmt.Sprintf("https://www.xxxxx/day?time=%s", time))
When calling the procedure normally, use the function pointer to http.Client.Get, and for your test pass in a pointer to your mock. It's not ideal, but I've not seen a nicer way around mocking non-interface external calls - and given its an external dependency, injecting it from the outside is not a bad thing.

How do I test an error on reading from a request body?

I'm writing unit tests for http Handlers in golang. When looking at code coverage reports of this I am running into the following issue: When reading the request body from a request, ioutil.ReadAll might return an error that I need to handle. Yet, when I write unit tests for my handler I do not know how to send a request to my handler in a way that it will trigger such an error (premature end of content seems not to generate such an error but will generate an error on unmarshaling the body). This is what I am trying to do:
package demo
import (
"bytes"
"io/ioutil"
"net/http"
"net/http/httptest"
"testing"
)
func HandlePostRequest(w http.ResponseWriter, r *http.Request) {
body, bytesErr := ioutil.ReadAll(r.Body)
if bytesErr != nil {
// intricate logic goes here, how can i test it?
http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest)
return
}
defer r.Body.Close()
// continue...
}
func TestHandlePostRequest(t *testing.T) {
ts := httptest.NewServer(http.HandlerFunc(HandlePostRequest))
data, _ := ioutil.ReadFile("testdata/fixture.json")
res, err := http.Post(ts.URL, "application/json", bytes.NewReader(data))
// continue...
}
How can I write a test case for HandlePostRequest that also covers the case of bytesErr not being nil?
You may create and use an http.Request forged by you, which deliberately returns an error when reading its body. You don't necessarily need a whole new request, a faulty body is enough (which is an io.ReadCloser).
Simplest achieved by using the httptest.NewRequest() function where you can pass an io.Reader value which will be used (wrapped to be an io.ReadCloser) as the request body.
Here's an example io.Reader which deliberately returns an error when attempting to read from it:
type errReader int
func (errReader) Read(p []byte) (n int, err error) {
return 0, errors.New("test error")
}
Example that will cover your error case:
func HandlePostRequest(w http.ResponseWriter, r *http.Request) {
defer r.Body.Close()
body, err := ioutil.ReadAll(r.Body)
if err != nil {
fmt.Printf("Error reading the body: %v\n", err)
return
}
fmt.Printf("No error, body: %s\n", body)
}
func main() {
testRequest := httptest.NewRequest(http.MethodPost, "/something", errReader(0))
HandlePostRequest(nil, testRequest)
}
Output (try it on the Go Playground):
Error reading the body: test error
See related question if you would need to simulate error reading from a response body (not from a request body): How to force error on reading response body

Go closure deadlock

Trying to mock http response in Go test. The code snippet below never terminates if I run it with
go test example.com/auth/...
package auth_test
import (
"testing"
"net/http/httptest"
"net/http"
)
func TestAuthorization(t *testing.T) {
t.Log("Should return 401 when Gateway returns 401")
{
url := oneOffUrlWithResponseCode(http.StatusUnauthorized)
request, _ := http.NewRequest("GET", url, nil)
response, _ := http.DefaultClient.Do(request)
if response.StatusCode != http.StatusUnauthorized {
t.Fatalf("Response should be 401 (Unauthorized)")
}
}
}
func oneOffUrlWithResponseCode(responseCode int) string {
var server *httptest.Server
server = httptest.NewServer(http.HandlerFunc(func(response http.ResponseWriter, request *http.Request) {
defer server.Close()
response.WriteHeader(responseCode)
}))
return server.URL
}
However, if I comment out this line
defer server.Close()
everything works fine.
Ideally, I do not want to "leak" *httptest.Server outside of oneOffUrlWithResponseCode function and obviously close it after first request.
Why it never terminates? What am I doing wrong? What is the right thing to do?
The program doesn't terminate because of a deadlock (and it has nothing to do with closures). You cannot call Close inside a handler because internally Close waits for all handlers to finish.
The easiest way to fix it is to "leak" httptest.Server outside of oneOffUrlWithResponseCode:
func TestAuthorization(t *testing.T) {
...
server := oneOffUrlWithResponseCode(http.StatusUnauthorized)
defer server.Close()
request, _ := http.NewRequest("GET", server.URL, nil)
...
}
func oneOffUrlWithResponseCode(responseCode int) *httptest.Server {
return httptest.NewServer(http.HandlerFunc(func(response http.ResponseWriter, request *http.Request) {
response.WriteHeader(responseCode)
}))
}