httptest.NewRequest how to include path with value?

I am trying to test http handler with what is described in Revisiting HTTP Handlers | main | Learn Go with tests

I have the following code (skimmed for brevity):

email := "myemail@test.me"

urlPath := fmt.Sprintf("http://test.com/%s", email)

request := httptest.NewRequest(http.MethodPost, urlPath, nil)
response := httptest.NewRecorder()

handleEmailCheck(response, request)

The route is something like

router.HandleFunc("POST /users/{email}", handleEmailCheck)

I believe the route/router is not being used since I am only testing the handler in isolation. I have tried with

urlPath := fmt.Sprintf("http://test.com/users/%s", email)

urlPath := fmt.Sprintf("/%s", email)

But it always gives me 400 because the following fails (returns “”)

email := r.PathValue("email")

If I run the app and test with Postman, it is working fine. Is there any special requirements or setup needed for httptest in order to pass path value?

Try:

r, err := http.NewRequest(method, path, body)
r.SetPathValue("key", "value")

Example in:

1 Like

Exactly. In your test you need to do something to set up your mux/routes. Check out the examples in http/httptest. This is a post that shows how you might write some tests:

And another approach is to split out your mux logic and then just re-use that in your tests. Something along the lines of this:

Going with the second option, let’s create a contrived example based on what you are trying to test:

func main() {
	mux := newAppMux()
	http.ListenAndServe(":9090", mux)
}

func newAppMux() *http.ServeMux {
	router := http.NewServeMux()
	router.HandleFunc("POST /users/{email}", handleEmailCheck)
	return router
}

func handleEmailCheck(w http.ResponseWriter, r *http.Request) {
	email := r.PathValue("email")
	if len(email) > 0 {
		fmt.Fprintf(w, `{ "email" : "%v" }`, email)
	} else {
		w.WriteHeader(http.StatusNotFound)
		fmt.Fprint(w, http.StatusText(http.StatusNotFound))
	}
}

To test it:

func TestHandleEmailCheck(t *testing.T) {
	ts := httptest.NewServer(newAppMux())
	defer ts.Close()

	client := ts.Client()
	res, err := client.Post(fmt.Sprintf("%v/users/test@test.com", ts.URL), "application/json", strings.NewReader(`{ "request": "body" }`))
	if err != nil {
		t.Errorf("Wasn't expecting error. Got: %v", err)
	}

	resBody, err := io.ReadAll(res.Body)
	res.Body.Close()
	if err != nil {
		t.Errorf("Wasn't expecting error. Got: %v", err)
	}
	expecting := `{ "email" : "test@test.com" }`
	if string(resBody) != expecting {
		t.Errorf("Unexpected response.\n\nExpected: %v\nGot: %s", expecting, resBody)
	}
}

Hopefully this is enough to get you started!

1 Like

Thank your very much, both of you. I have tried both approach and it is working as expected. However, I am going with @GonzaSaya’s approach since it is much simpler and cleaner in my opinion.

It’s a question of what you are testing. Enable code coverage and test both solutions to see what I mean. That said, both approaches are fine and I’m glad you found a solution!

1 Like

Yes, you are correct. I think your approach is more suited for integration test? I am planning to do a separate integration test part later on so I will definitely go with mux one by mocking the server.