Pada modul ini kita akan membuat fungsi test untuk fungsi helper UploadFiles.
Pertama tambahkan direktori uploads pada cmd/web/testdata.
Kemudian download file image test di https://drive.google.com/file/d/1wcYc7NMp2qFzK2QrsqsjbtjDm8Q18_H2/view?usp=share_link
Ekstrak file download diatas, dan simpan didalam direktori cmd/web/testdata.
Kemudian kita buka file cmd/web/handlers_test.go, dan tambahkan fungsi test serta fungsi helper seperti berikut.
package main
import (
"context"
"crypto/tls"
"fmt"
"image"
"image/png"
"io"
"mime/multipart"
"net/http"
"net/http/httptest"
"net/url"
"os"
"path"
"strings"
"sync"
"testing"
)
func Test_application_handlers(t *testing.T) {
var theTests = []struct {
name string
url string
expectedStatusCode int
expectedURL string
expectedFirstStatusCode int
}{
{"home", "/", http.StatusOK, "/", http.StatusOK},
{"404", "/fish", http.StatusNotFound, "/fish", http.StatusNotFound},
{"profile", "/user/profile", http.StatusOK, "/", http.StatusTemporaryRedirect},
}
routes := app.routes()
// create a test server
ts := httptest.NewTLSServer(routes)
defer ts.Close()
// If we want to get the first status code, we have to create our
// own http client with a custom CheckRedirect function, and limit
// it ot the first response. For testing, we also need to
// specify a custom Transport field which accepts insecure
// https certificates. First create the custom transport.
tr := &http.Transport{
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
}
// Then create the custom client.
client := &http.Client{
Transport: tr,
CheckRedirect: func(req *http.Request, via []*http.Request) error {
return http.ErrUseLastResponse
},
}
// range through test data
for _, e := range theTests {
resp, err := ts.Client().Get(ts.URL + e.url)
if err != nil {
t.Log(err)
t.Fatal(err)
}
if resp.StatusCode != e.expectedStatusCode {
t.Errorf("for %s: expected status %d, but got %d", e.name, e.expectedStatusCode, resp.StatusCode)
}
if resp.Request.URL.Path != e.expectedURL {
t.Errorf("%s: expected final url of %s but got %s", e.name, e.expectedURL, resp.Request.URL.Path)
}
// Call the test server using our custom http client
// which does not follow redirects, and which has a custom
// transport.
resp2, _ := client.Get(ts.URL + e.url)
if resp2.StatusCode != e.expectedFirstStatusCode {
t.Errorf("%s: expected first returned status code to be %d but got %d", e.name, e.expectedFirstStatusCode, resp2.StatusCode)
}
}
}
func TestAppHome(t *testing.T) {
var tests = []struct {
name string
putInSession string
expectedHTML string
}{
{"first visit", "", "<small>Session:"},
{"second visit", "hello, skillplus!", "<small>Session: hello, skillplus!"},
}
for _, e := range tests {
// create a request
req, _ := http.NewRequest("GET", "/", nil)
req = addCtxSessToReq(req, app)
_ = app.Session.Destroy(req.Context())
if e.putInSession != "" {
app.Session.Put(req.Context(), "test", e.putInSession)
}
rr := httptest.NewRecorder()
handler := http.HandlerFunc(app.Home)
handler.ServeHTTP(rr, req)
// check status code
if rr.Code != http.StatusOK {
t.Errorf("TestAppHome returned wrong status code; expected 200 but got %d", rr.Code)
}
body, _ := io.ReadAll(rr.Body)
if !strings.Contains(string(body), e.expectedHTML) {
t.Errorf("%s: did not find %s in response body", e.name, e.expectedHTML)
}
}
}
func TestApp_renderWithBadTemplate(t *testing.T) {
// set pathToTemplates to a location with a bad template
templatePath = "./testdata/"
req, _ := http.NewRequest("GET", "/", nil)
req = addCtxSessToReq(req, app)
rr := httptest.NewRecorder()
err := app.render(rr, req, "bad.page.gohtml", &TemplateData{})
if err == nil {
t.Error("expected error from bad template, but did not get one")
}
templatePath = "./../../templates/"
}
func getCtx(req *http.Request) context.Context {
ctx := context.WithValue(req.Context(), contextUserKey, "unknown")
return ctx
}
func addCtxSessToReq(req *http.Request, app application) *http.Request {
req = req.WithContext(getCtx(req))
ctx, _ := app.Session.Load(req.Context(), req.Header.Get("X-Session"))
return req.WithContext(ctx)
}
func Test_app_Login(t *testing.T) {
var tests = []struct {
name string
postedData url.Values
expectedStatusCode int
expectedLoc string
}{
{
name: "valid login",
postedData: url.Values{
"email": {"admin@example.com"},
"password": {"secret"},
},
expectedStatusCode: http.StatusSeeOther,
expectedLoc: "/user/profile",
},
{
name: "missing form data",
postedData: url.Values{
"email": {""},
"password": {""},
},
expectedStatusCode: http.StatusSeeOther,
expectedLoc: "/",
},
{
name: "user not found",
postedData: url.Values{
"email": {"you@there.com"},
"password": {"password"},
},
expectedStatusCode: http.StatusSeeOther,
expectedLoc: "/",
},
{
name: "bad credentials",
postedData: url.Values{
"email": {"admin@example.com"},
"password": {"password"},
},
expectedStatusCode: http.StatusSeeOther,
expectedLoc: "/",
},
}
for _, e := range tests {
req, _ := http.NewRequest("POST", "/login", strings.NewReader(e.postedData.Encode()))
req = addCtxSessToReq(req, app)
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
rr := httptest.NewRecorder()
handler := http.HandlerFunc(app.Login)
handler.ServeHTTP(rr, req)
if rr.Code != e.expectedStatusCode {
t.Errorf("%s: returned wrong status code; expected %d, but got %d", e.name, e.expectedStatusCode, rr.Code)
}
actualLoc, err := rr.Result().Location()
if err == nil {
if actualLoc.String() != e.expectedLoc {
t.Errorf("%s: expected location %s but got %s", e.name, e.expectedLoc, actualLoc.String())
}
} else {
t.Errorf("%s: no location header set", e.name)
}
}
}
func Test_app_UploadFiles(t *testing.T) {
// set up pipes
pr, pw := io.Pipe()
// create a new writer, of type *io.Writer
writer := multipart.NewWriter(pw)
// create a waitgroup, and add 1 to it
wg := &sync.WaitGroup{}
wg.Add(1)
// simulate uploading a file using a goroutine and our writer
go simulatePNGUpload("./testdata/img.png", writer, t, wg)
// read from the pipe which receives data
request := httptest.NewRequest("POST", "/", pr)
request.Header.Add("Content-Type", writer.FormDataContentType())
// call app.UploadFiles
uploadedFiles, err := app.UploadFiles(request, "./testdata/uploads/")
if err != nil {
t.Error(err)
}
// perform our tests
if _, err := os.Stat(fmt.Sprintf("./testdata/uploads/%s", uploadedFiles[0].OriginalFileName)); os.IsNotExist(err) {
t.Errorf("expected file to exist: %s", err.Error())
}
// clean up
_ = os.Remove(fmt.Sprintf("./testdata/uploads/%s", uploadedFiles[0].OriginalFileName))
}
func simulatePNGUpload(fileToUpload string, writer *multipart.Writer, t *testing.T, wg *sync.WaitGroup) {
defer writer.Close()
defer wg.Done()
// create the form data filed 'file' with value being filename
part, err := writer.CreateFormFile("file", path.Base(fileToUpload))
if err != nil {
t.Error(err)
}
// open the actual file
f, err := os.Open(fileToUpload)
if err != nil {
t.Error(err)
}
defer f.Close()
// decode the image
img, _, err := image.Decode(f)
if err != nil {
t.Error("error decoding image:", err)
}
// write the png to our io.Writer
err = png.Encode(part, img)
if err != nil {
t.Error(err)
}
}
Jika kita jalankan test pada direktori cmd/web, sesuai ekspektasi fungsi UploadFiles berhasil ditest.
$ go test -v -run Test_app_UploadFiles
=== RUN Test_app_UploadFiles
--- PASS: Test_app_UploadFiles (0.01s)
PASS
ok webapp/cmd/web 0.413s
Pada modul selanjutnya kita akan membuat fungsi test untuk fungsi handler UploadProfilePic.