Nerd For Tech
Published in

Nerd For Tech

Golang: Testing function in a simple web server project

Testing is important when building a software project. In Golang, there are some packages really useful when doing the unit tests for the functions in our project. In this article, I will show how to use those packages to test a function in a web server project. the project structure is like below. We will test function as an example. How to use Golang to build a web server could see in my previous article.

The code in is like below.

// main.go

package main

import (
"log"
"net"
"net/http"
"my-project/greeting"
)

func main() {
http.HandleFunc("/greeting", greeting.Greeting)

log.Println("Starting server....")

listener, err := net.Listen("tcp", "localhost:8080")
if err != nil {
log.Fatal(err)
}

http.Serve(listener, nil)
}

And is like below.

// greeting.go

package greeting

import (
"fmt"
"net/http"
)

func Greeting(w http.ResponseWriter, r *http.Request) {
fmt.Fprint(w, "Hello World")
}

1. Introduction

Golang provide package for us to do unit tests and also provide package which could use for recording HTTP(or HTTPS) responses when doing the unit test. Following are the introductions of these two packages and the functions which are useful for doing unit tests:

Package provides support for automated testing of Go packages.

To write a new test suite, create a file whose name ends _test.go that contains the TestXxx functions as described here. Put the file in the same package as the one being tested. The file will be excluded from regular package builds but will be included when the “go test” command is run. For more detail, run “go help test” and “go help testflag”.

From

A sample test function would like below.

func TestAbs(t *testing.T) {
got := Abs(-1)
want := 1
if got != want {
t.Errorf("Abs(-1) = %d; want %d", got, want)
}
}

Package provides utilities for HTTP testing. It provides function to simulate an incoming server Request, suitable for passing to an http.Handler for testing. , , and should pass to function. The is HTTP methods like . The could be a path or an absolute , and the could be nil.

The provided body may be nil. If the body is of type *bytes.Reader, *strings.Reader, or *bytes.Buffer, the Request.ContentLength is set.

From

func NewRequest(method, target string, body io.Reader) *http.Request

The sample code for function.

request := httptest.NewRequest("GET", "http://example.com/greeting", nil)

Package provides a type that calls . It is an implementation of that records its mutations for later inspection in tests, which means that it could use for recording the content of the response for HTTP(or HTTPS) requests in tests.

type ResponseRecorder struct {
// Code is the HTTP response code set by WriteHeader.
Code int
// HeaderMap contains the headers explicitly set by the Handler.
// It is an internal detail.
HeaderMap http.Header
// Body is the buffer to which the Handler's Write calls are sent.
// If nil, the Writes are silently discarded.
Body *bytes.Buffer
// Flushed is whether the Handler called Flush.
Flushed bool
// contains filtered or unexported fields
}

Package also provides a function call that could return an initialized for recording the simulation of an HTTP(or HTTPS) response.

func NewRecorder() *ResponseRecorder

There is another function in package useful in the test. It is function.

func (rw *ResponseRecorder) Header() http.Header

In the sample code below. We could see after running the function, the could call function which will return the response generated by the handler. The returned Response will have at least its , , . must only be called after the handler has finished running.

handler := func(w http.ResponseWriter, r *http.Request) {
fmt.Fprint(w, "Hello World")
}
req := httptest.NewRequest("GET", "http://example.com/foo", nil)
w := httptest.NewRecorder()
handler(w, req)
resp := w.Result()
body := w.Body
fmt.Println(resp.StatusCode)
fmt.Println(resp.Header)
fmt.Println(body)
------------------------------------------------------------------------------
Output:
200
map[Content-Type:[text/plain; charset=utf-8]]
Hello World

2. Start testing

For testing, first, we need to add a file whose name ends that contains the TestXxx function because we will use the package and that’s the rule it works, so there will be a file in the same folder as . The structure of our project will be like the below picture.

We are going to test function first, so the function name in is . The content of would like below.

// main_test.go

package main
import (
"net/http"
"net/http/httptest"
"testing"
// we need to call Greeting function, so need to import greeting
"my-project/greeting"
)
func TestGreeting(t *testing.T) {
// simulate an incoming server Request
request, _ := http.NewRequest("GET", "/greeting", nil)

// record the simulation of HTTP response
response := httptest.NewRecorder()

// run the function we want to test
greeting.Greeting(response, request)

// check if the result is what we expect
got := response.Body.String()
want := "Hello World"
if got != want {

// if the result is not correct print error
t.Errorf("got %v, want %v", got, want)
}
}

To run the test, we need to run command in the root path of our project in the terminal. If you using as the editor, after installing Golang extension, we could run the test by clicking a button right next to the line of testing function.

After clicking the run button, we could see the result in terminal. If the testing pass, it will show a green check right next to the testing function, and also it will print the log and show in the terminal.

If the test failed, it will show the error we got.

If we want to add some description in the test log, like more detail about what we are testing, we could use to wrap our testing function like below.

// main_test.go

package main
import (
"net/http"
"net/http/httptest"
"testing"
"my-project/greeting"
)
func TestGreeting(t *testing.T) {

// use t.Run to wrap your test
t.Run("test greeting ok", func(t *testing.T) {
request, _ := http.NewRequest("GET", "/greeting", nil)

response := httptest.NewRecorder()

greeting.Greeting(response, request)

got := response.Body.String()
want := "Hello World"

if got != want {
t.Errorf("got %v, want %v", got, want)
}
})
}

And after running the test, we can see the description we put in shows in the log.

3. Refactoring code

We now write our test in , but it is not a good place to run the test. When people see the file, it is hard to know what it is going to test, so we should create a test file with a name that includes the function we want to test and place it in the folder where the function file is. It will be easier for other people associate to with those functions we are testing.

Like we have function in which in folder, we should place the test file in the same folder, and name it as so when everyone sees the file could know it is testing for the function in . The structure of the project will become like below.

And is like below. We should move the test function in .

// greeting_test.go

package greeting
import (
"net/http"
"net/http/httptest"
"testing"
)
func TestGreeting(t *testing.T) {
t.Run("test greeting ok", func(t *testing.T) {
request, _ := http.NewRequest("GET", "/greeting", nil)
response := httptest.NewRecorder()
Greeting(response, request)
got := response.Body.String()
want := "Hello World"
if got != want {
t.Errorf("got %v, want %v", got, want)
}
})
}

Clicking the run test button, the test function should still run successfully.

Now we know how to do the unit test, it’s also important to know what is the test coverage of our code. We could run in the root of our project in the terminal to see the result.

In the test result, we could see in folder, the test coverage is 100%, because there is only function in the folder needs to be tested and we already have a test case for it in .

Reference

Learn Go with test

Go official online document

使用Golang打造web 應用程式

Build Web Application with Golang

--

--

NFT is an Educational Media House. Our mission is to bring the invaluable knowledge and experiences of experts from all over the world to the novice. To know more about us, visit https://www.nerdfortech.org/.

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store
icelandcheng

Programming Skill learner and Sharer | Ruby on Rails | Golang | Vue.js | Web Map API