Specifying the Requirement for the Route Handler With a Unit Test
Before we create the handler for the index route, we will create a test to define the expected behavior of this route handler. This test will check for the following conditions:
- The handler responds with an HTTP status code of 200,
- The returned HTML contains a title tag containing the text
Home Page
.
The code for the test will be placed in the TestShowIndexPageUnauthenticated
function in the handlers.article_test.go
file. We will place helper functions used by this function in the common_test.go
file.
The content of handlers.article_test.go
is as follows:
// handlers.article_test.go
package main
import (
"io/ioutil"
"net/http"
"net/http/httptest"
"strings"
"testing"
)
// Test that a GET request to the home page returns the home page with
// the HTTP code 200 for an unauthenticated user
func TestShowIndexPageUnauthenticated(t *testing.T) {
r := getRouter(true)
r.GET("/", showIndexPage)
// Create a request to send to the above route
req, _ := http.NewRequest("GET", "/", nil)
testHTTPResponse(t, r, req, func(w *httptest.ResponseRecorder) bool {
// Test that the http status code is 200
statusOK := w.Code == http.StatusOK
// Test that the page title is "Home Page"
// You can carry out a lot more detailed tests using libraries that can
// parse and process HTML pages
p, err := ioutil.ReadAll(w.Body)
pageOK := err == nil && strings.Index(string(p), "<title>Home Page</title>") > 0
return statusOK && pageOK
})
}
The content of common_test.go
is as follows:
package main
import (
"net/http"
"net/http/httptest"
"os"
"testing"
"github.com/gin-gonic/gin"
)
var tmpArticleList []article
// This function is used for setup before executing the test functions
func TestMain(m *testing.M) {
//Set Gin to Test Mode
gin.SetMode(gin.TestMode)
// Run the other tests
os.Exit(m.Run())
}
// Helper function to create a router during testing
func getRouter(withTemplates bool) *gin.Engine {
r := gin.Default()
if withTemplates {
r.LoadHTMLGlob("templates/*")
}
return r
}
// Helper function to process a request and test its response
func testHTTPResponse(t *testing.T, r *gin.Engine, req *http.Request, f func(w *httptest.ResponseRecorder) bool) {
// Create a response recorder
w := httptest.NewRecorder()
// Create the service and process the above request.
r.ServeHTTP(w, req)
if !f(w) {
t.Fail()
}
}
// This function is used to store the main lists into the temporary one
// for testing
func saveLists() {
tmpArticleList = articleList
}
// This function is used to restore the main lists from the temporary one
func restoreLists() {
articleList = tmpArticleList
}
To implement this test, we have written some helper functions. These will also help us reduce boilerplate code when we write additional tests to test similar functionality.
The TestMain
function sets Gin to use the test mode and calls the rest of the test functions. The getRouter
function creates and returns a router in a manner similar to the main application. The saveLists()
function saves the original article list in a temporary variable. This temporary variable is used by the restoreLists()
function to restore the article list to its initial state after a unit test is executed.
Finally, the testHTTPResponse
function executes the function passed in to see if it returns a boolean true value — indicating a successful test, or not. This function helps us avoid duplicating the code needed to test the response of an HTTP request.
To check the HTTP code and the returned HTML, we'll do the following:
- Create a new router,
- Define a route to use the same handler that the main app uses (showIndexPage),
- Create a new request to access this route,
- Create a function that processes the response to test the HTTP code and HTML, and
- Call
testHTTPResponse()
with this new function to complete the test.