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 &amp; 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.

results matching ""

    No results matching ""