programming

How to Create Integration Tests in Golang

Carrying out integration tests for APIs means that we must at least be able to run the application first so that integrated testing can be carried out. We need to prepare several cases, test cases that cover the needs of the integration test. For example, the API Endpoint' that we have worked on has a database, cache` or other external resource that is related to the continuity of the API Endpoint.

So, we will simulate how to carry out an API integration test. The first stage is that we create an API Service where the API is used for number addition operations.

Create API Programs

The following is the API Endpoint program that will be tested or an integration test will be created.

package main

import (
	"encoding/json"
	"fmt"
	"net/http"
	"strconv"
)

func main() {
	http.HandleFunc("/add", HandleAddInts)
	address := ":9000"
	fmt.Printf("Memulai server at %s\n", address)
	fmt.Println()
	fmt.Println("contoh query: /add?a=2&b=2&authtoken=abcdef123")
	fmt.Println("tokennya adalah 'abcdef123'")
	err := http.ListenAndServe(address, nil)
	if err != nil {
		fmt.Printf("error ketika start server: %v", err)
	}
}

type TambahResponse struct {
	Result int `json:"result"`
}

func HandleAddInts(w http.ResponseWriter, r *http.Request) {
	params := r.URL.Query()
	paramA := params.Get("a")
	paramB := params.Get("b")
	token := params.Get("authtoken")

	if paramA == "" || paramB == "" || token == "" {
		http.Error(w, "tidak ada parameters", http.StatusBadRequest)
		return
	}

	if token != "abcdef123" {
		http.Error(w, "token tidak valid", http.StatusUnauthorized)
		return
	}

	intA, err := strconv.Atoi(paramA)
	if err != nil {
		http.Error(w, "parameter 'a' harus integer", http.StatusBadRequest)
		return
	}

	intB, err := strconv.Atoi(paramB)
	if err != nil {
		http.Error(w, "parameter 'b' harus integer", http.StatusBadRequest)
		return
	}

	response := TambahResponse{
		Result: intA + intB,
	}

	json, err := json.MarshalIndent(&response, "", " ")
	if err != nil {
		http.Error(w, "error while marshalling", http.StatusInternalServerError)
		return
	}

	fmt.Fprint(w, string(json))
}

In this program there are several validations when the program is run, namely * The parameters entered must be integer

  • Do not fill in the parameters a and b then it will become
  • Test if it doesn’t send authToken

After the above program is complete, we run it with the command

➜  integration-test git:(main) ✗ go run app/main.go
Memulai server at :9000

contoh query: /add?a=2&b=2&authtoken=abcdef123
tokennya adalah 'abcdef123'

If the program has run successfully, it will be visible or can be accessed using postman and curl with endpoint and port :9000.

➜  materi curl 'http://localhost:9000/add?a=2&b=2&authtoken=abcdef123'
{
 "result": 4
}%   

If the program is successful as above then, we have run the API program to add numbers using the http protocol.

Create Integration Tests

Next, we will create an API integration test that we created earlier with several cases mentioned above.

Prepare the program or file main.go to create API Testing.

package integrationtest

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"net/http"
)

type MathClient struct {
	Token string
	Host  string
}

type AddResult struct {
	Result int `json:"result"`
}

func (c *MathClient) APISum(i, j int) (int, error) {
	query := fmt.Sprintf("http://%s/add?a=%v&b=%v&authtoken=%v", c.Host, i, j, c.Token)

	response, err := http.Get(query)
	if err != nil {
		return 0, err
	}
	defer response.Body.Close()

	data, err := ioutil.ReadAll(response.Body)
	if err != nil {
		return 0, err
	}

	a := AddResult{}
	err = json.Unmarshal(data, &a)
	if err != nil {
		return 0, err
	}

	return a.Result, nil
}

We will use the APISum method to access the http API that we have created as the main server which we will test using this integration test.

Create Client Struct

Create a struct for the API structure and response like this.

type MathClient struct {
	Token string
	Host  string
}

type AddResult struct {
	Result int `json:"result"`
}

Create a Sum API Access Method

then we will create a method to retrieve the API sum data.

func (c *MathClient) APISum(i, j string) (int, int, error) {
	query := fmt.Sprintf("http://%s/add?a=%s&b=%s&authtoken=%v", c.Host, i, j, c.Token)

	var statusCode int = http.StatusBadRequest
	response, err := http.Get(query)
	if err != nil {
		return 0, statusCode, err
	}
	defer response.Body.Close()
	statusCode = response.StatusCode
	data, err := ioutil.ReadAll(response.Body)
	if err != nil {
		return 0, statusCode, err
	}

	a := AddResult{}
	err = json.Unmarshal(data, &a)
	if err != nil {
		return 0, statusCode, err
	}

	return a.Result, statusCode, nil
}

Create a Unit Test which is used as an Integration Test

Next, we will create this API Integration testing in this method. We can make a program like this or just generate in the APISum method.

func TestMathClient_APISum(t *testing.T) {
	type fields struct {
		Token string
		Host  string
	}
	type args struct {
		i string
		j string
	}
	tests := []struct {
		name     string
		fields   fields
		args     args
		want     int
		wantErr  bool
		wantCode int
	}{
		// testing test case
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			c := &MathClient{
				Token: tt.fields.Token,
				Host:  tt.fields.Host,
			}
			got, gotCode, err := c.APISum(tt.args.i, tt.args.j)
			if (err != nil) != tt.wantErr {
				t.Errorf("MathClient.APISum() error = %v, wantErr %v", err, tt.wantErr)
				return
			}
			if got != tt.want {
				t.Errorf("MathClient.APISum() = %v, want %v", gotCode, tt.wantCode)
			}
			if gotCode != tt.wantCode {
				t.Errorf("MathClient.APISum() = %v, want %v", gotCode, tt.wantCode)
			}
		})
	}
}

Add Test Cases

Then, so that we can carry out case by case testing, we need to fill in the number of cases in the curly brackets above with contents like this.

{
  name: "case sukses jumlah data",
  fields: fields{
    Token: "abcdef123",
    Host:  "localhost:9000",
  },
  args: args{
    i: "2",
    j: "2",
  },
  want:     4,
  wantErr:  false,
  wantCode: http.StatusOK,
},
{
  name: "token tidak diset",
  fields: fields{
    Token: "abc",
    Host:  "localhost:9000",
  },
  args: args{
    i: "2",
    j: "2",
  },
  want:     0,
  wantErr:  true,
  wantCode: http.StatusUnauthorized,
},
{
  name: "host tidak sesuai",
  fields: fields{
    Token: "abcdef123",
    Host:  "localhost:500",
  },
  args: args{
    i: "2",
    j: "2",
  },
  want:     0,
  wantErr:  true,
  wantCode: http.StatusBadRequest,
},
{
  name: "case parameter kosong",
  fields: fields{
    Token: "abcdef123",
    Host:  "localhost:9000",
  },
  args: args{
    i: "",
    j: "",
  },
  want:     0,
  wantErr:  true,
  wantCode: http.StatusBadRequest,
},
{
  name: "case parameter bukan integer",
  fields: fields{
    Token: "abcdef123",
    Host:  "localhost:9000",
  },
  args: args{
    i: "a",
    j: "a",
  },
  want:     0,
  wantErr:  true,
  wantCode: http.StatusBadRequest,
},

Run Integration Test

Everything has been completed for testing and creating tests. Don’t forget that because this is integration test code, we need to add something so that when we do go test -v we can add the code above like this.

//go:build integration

We continue by running go test with the integration parameter

$ go test -v -tags=integration

Also make sure the API Endpoint program is running

➜  integration-test git:(main) ✗ go run app/main.go
Memulai server at :9000

contoh query: /add?a=2&b=2&authtoken=abcdef123
tokennya adalah 'abcdef123'
comments powered by Disqus