gosubmit: Parse HTML forms and build pre-filled requests for submitting

While writing the tests for rondoBB, I realized I was writing too much code to test simple form submissions. In fact, it was so verbose that it took a really long time to write these tests and I writing them. Since there is a CSRF token on every form, I had to extract it them from the GET request response and fill it in manually to make the test to work. There are also some <input type="hidden"> fields whose values are filled on the server-side. Even though I had helper functions written for this, it was still a pain to use them.

I first discovered the amazing goquery library, but it didn't help reduce the amount of code so I realized I had to write the right tool for the job: gosubmit.

gosubmit uses x/net/html to automatically parse the HTML from a *http.Response body and create a list of all found <form> elements in the document. Additionally, it parses all input values on the form so the user doesn't need to fill the values manually that are already there.

Below is an usage example. Let's say we'd like to test a handler that looks like this:

 1package app
 2
 3import (
 4	"net/http"
 5)
 6
 7func Serve(w http.ResponseWriter, r *http.Request) {
 8	switch r.Method {
 9	case http.MethodPost:
10		username := r.FormValue("username")
11		password := r.FormValue("password")
12		csrf := r.FormValue("csrf")
13		if csrf == "1234" && username == "user" && password == "pass" {
14			w.Write([]byte("Welcome, " + username))
15			return
16		}
17		w.WriteHeader(http.StatusForbidden)
18	default:
19		w.Write([]byte(`<!DOCTYPE html>
20<html>
21<body>
22<form name="test" method="POST">
23	<input type="text" name="username">
24	<input type="password" name="password">
25	<input type="hidden" name="csrf" value="1234">
26	<input type="submit">
27</form>`))
28	}
29}
30
31var mux *http.ServeMux
32
33func init() {
34	mux = http.NewServeMux()
35	mux.HandleFunc("/auth/login", Serve)
36}

Our test can look like:

 1package app
 2
 3import (
 4	"net/http/http"
 5	"net/http/httptest"
 6	"testing"
 7
 8	. "github.com/jeremija/gosubmit"
 9)
10
11func TestLogin(t *testing.T) {
12	w := httptest.NewRecorder()
13	r := httptest.NewRequest("GET", "/auth/login", nil)
14	mux.ServeHTTP(w, r)
15
16	r = ParseResponse(w.Result(), r.URL).
17		FirstForm().
18		Testing(t).
19		NewTestRequest(
20			Set("username", "user"),
21			Set("password", "pass"),
22		)
23	w = httptest.NewRecorder()
24	mux.ServeHTTP(w, r)
25
26	if w.Code != http.StatusOK {
27		t.Fatalf("Expected status %d, but got %d", http.StatusOK, w.Code)
28	}
29}

Note that there was no need to call Set("csrf", "1234") - gosubmit already found the value of it by parsing the GET request.

For more information, checkout the gosubmit source code.

1
Please to comment
No comments yet - be the first to comment :)