Writing Tests for This Functionality
Based on our requirements, we know that our code should:
- Prevent registration if a username is taken,
- Allow registration if details are valid, and
- Show the registration page to an unauthenticated user.
Before we start writing tests, let's modify common_test.go
to add a temporary holder for users (tmpUserList
) and modify the saveLists
and restoreLists
functions to process this new list. The changes in the updated common_test.go
file should be as follows:
var tmpUserList []user
// existing code (not shown)
// .
// .
// .
func saveLists() {
tmpUserList = userList
tmpArticleList = articleList
}
func restoreLists() {
userList = tmpUserList
articleList = tmpArticleList
}
To test the functionality in models.user.go
, we'll need the following tests in models.user_test.go
:
1. TestUsernameAvailability
This test will check that the isUsernameAvailable
function returns true when a new username is passed in and it returns false when an existing username is passed in, as follows:
// models.user_test.go
func TestUsernameAvailability(t *testing.T) {
saveLists()
if !isUsernameAvailable("newuser") {
t.Fail()
}
if isUsernameAvailable("user1") {
t.Fail()
}
registerNewUser("newuser", "newpass")
if isUsernameAvailable("newuser") {
t.Fail()
}
restoreLists()
}
2. TestValidUserRegistration
This test will check that the registerNewUser
function successfully registers a new user with an unused username, as follows:
// models.user_test.go
func TestValidUserRegistration(t *testing.T) {
saveLists()
u, err := registerNewUser("newuser", "newpass")
if err != nil || u.Username == "" {
t.Fail()
}
restoreLists()
}
3. TestInvalidUserRegistration
This test will check that the registerNewUser
function doesn't allow a user with an invalid username and password pair to register, as follows:
// models.user_test.go
func TestInvalidUserRegistration(t *testing.T) {
saveLists()
u, err := registerNewUser("user1", "pass1")
if err == nil || u != nil {
t.Fail()
}
u, err = registerNewUser("newuser", "")
if err == nil || u != nil {
t.Fail()
}
restoreLists()
}
We'll add the tests for handlers.user.go
in handlers.user_test.go
. Before we do that, let's create a couple of helper functions that will return username and password pairs for logging in and for registration:
//handlers.user_test.go
func getLoginPOSTPayload() string {
params := url.Values{}
params.Add("username", "user1")
params.Add("password", "pass1")
return params.Encode()
}
func getRegistrationPOSTPayload() string {
params := url.Values{}
params.Add("username", "u1")
params.Add("password", "p1")
return params.Encode()
}
Now, let's add the following tests:
1. TestShowRegistrationPageUnauthenticated
This test will check that the registration page is shown to unauthenticated users. To implement this test, we first need to create a router and associate the /u/register
route with the showRegistrationPage
route handler. We will then create a new HTTP GET request to access this route.
Finally, we will use the testHTTPResponse
helper function (explained in part 1 of the tutorial) to check that the HTTP response code is 200 and that the registration page is served as expected.
//handlers.user_test.go
func TestShowRegistrationPageUnauthenticated(t *testing.T) {
r := getRouter(true)
r.GET("/u/register", showRegistrationPage)
req, _ := http.NewRequest("GET", "/u/register", nil)
testHTTPResponse(t, r, req, func(w *httptest.ResponseRecorder) bool {
statusOK := w.Code == http.StatusOK
p, err := ioutil.ReadAll(w.Body)
pageOK := err == nil && strings.Index(string(p), "<title>Register</title>") > 0
return statusOK && pageOK
})
}
2. TestRegisterUnauthenticated
This test will check that a registration request with a new username succeeds. In this test, we will create an HTTP POST request to test the /u/register
route and the register
route handler using an unused username.
This test should pass if the HTTP status code is 200 and if the user is successfully registered and logged in.
//handlers.user_test.go
func TestRegisterUnauthenticated(t *testing.T) {
saveLists()
w := httptest.NewRecorder()
r := getRouter(true)
r.POST("/u/register", register)
registrationPayload := getRegistrationPOSTPayload()
req, _ := http.NewRequest("POST", "/u/register", strings.NewReader(registrationPayload))
req.Header.Add("Content-Type", "application/x-www-form-urlencoded")
req.Header.Add("Content-Length", strconv.Itoa(len(registrationPayload)))
r.ServeHTTP(w, req)
if w.Code != http.StatusOK {
t.Fail()
}
p, err := ioutil.ReadAll(w.Body)
if err != nil || strings.Index(string(p), "<title>Successful registration & Login</title>") < 0 {
t.Fail()
}
restoreLists()
}
3. TestRegisterUnauthenticatedUnavailableUsername
This test will check that a user cannot register with a username that is already in use. In this test, we will create an HTTP POST request to test the /u/register
route and the register
route handler with an unavailable username.
This test should pass if the request fails with an HTTP status code of 400.
//handlers.user_test.go
func TestRegisterUnauthenticatedUnavailableUsername(t *testing.T) {
saveLists()
w := httptest.NewRecorder()
r := getRouter(true)
r.POST("/u/register", register)
registrationPayload := getLoginPOSTPayload()
req, _ := http.NewRequest("POST", "/u/register", strings.NewReader(registrationPayload))
req.Header.Add("Content-Type", "application/x-www-form-urlencoded")
req.Header.Add("Content-Length", strconv.Itoa(len(registrationPayload)))
r.ServeHTTP(w, req)
if w.Code != http.StatusBadRequest {
t.Fail()
}
restoreLists()
}
Now that we have the tests written, let's run them to see what happens. In your project directory, execute the following command:
go test -v
Executing this command should show the following output:
=== RUN TestShowIndexPageUnauthenticated
[GIN] 2016/08/04 - 08:37:31 | 200 | 199.765µs | | GET /
--- PASS: TestShowIndexPageUnauthenticated (0.00s)
=== RUN TestArticleUnauthenticated
[GIN] 2016/08/04 - 08:37:31 | 200 | 81.056µs | | GET /article/view/1
--- FAIL: TestValidUserRegistration (0.00s)
=== RUN TestInvalidUserRegistration
--- PASS: TestInvalidUserRegistration (0.00s)
=== RUN TestUsernameAvailability
--- FAIL: TestUsernameAvailability (0.00s)
--- PASS: TestArticleListJSON (0.00s)
=== RUN TestArticleXML
FAIL
exit status 1
FAIL--- PASS: TestArticleUnauthenticated (0.00s)
=== RUN TestArticleListJSON
[GIN] 2016/08/04 - 08:37:31 | 200 | 58.196µs | | GET /
[GIN] 2016/08/04 - 08:37:31 | 200 | 36.732µs | | GET /article/view/1
--- PASS: TestArticleXML (0.00s)
=== RUN TestShowRegistrationPageUnauthenticated
[GIN] 2016/08/04 - 08:37:31 | 200 | 432ns | | GET /u/register
--- FAIL: TestShowRegistrationPageUnauthenticated (0.00s)
=== RUN TestRegisterUnauthenticated
[GIN] 2016/08/04 - 08:37:31 | 200 | 366ns | | POST /u/register
--- FAIL: TestRegisterUnauthenticated (0.00s)
=== RUN TestRegisterUnauthenticatedUnavailableUsername
[GIN] 2016/08/04 - 08:37:31 | 200 | 385ns | | POST /u/register
--- FAIL: TestRegisterUnauthenticatedUnavailableUsername (0.00s)
=== RUN TestGetAllArticles
--- PASS: TestGetAllArticles (0.00s)
=== RUN TestGetArticleByID
--- PASS: TestGetArticleByID (0.00s)
=== RUN TestValidUserRegistration
github.com/demo-apps/go-gin-app 0.007s
The new tests are failing, as expected, because we haven't yet implemented the functionality in models.user.go
and handlers.user.go
.