Golang Unit Testing with examples
Tags: go, testing, programming
Published: 2023-03-21 06:51:57

Unit testing is a key aspect of software development that helps ensure the correctness and reliability of the code. In Go, the process of unit testing is relatively straightforward, but it requires a little bit of planning and effort.

In this article, we'll cover the basics of unit testing in Go, including how to write unit tests, run them, and interpret the results. We'll also provide some practical examples of how to apply unit testing to real-world scenarios.

## Writing Unit Tests in Go

The first step in writing a unit test is to create a new file with the suffix `_test.go` in the same package as the code being tested. For example, if you're testing a package called math, you would create a file named math_test.go in the same directory as the math package.

Once you've created the test file, you need to import the testing package and define a function with the signature `func TestXxx(t *testing.T)`. The function name should start with Test and be followed by a descriptive name that indicates what the test is testing. For example, if you're testing a function named Add, you might define a test function called `TestAdd`.

Here's an example of a simple unit test:

```go
package math

import "testing"

func TestAdd(t *testing.T) {
    result := Add(2, 3)
    if result != 5 {
        t.Errorf("Add(2, 3) = %d; expected 5", result)
    }
}
```

In this example, we're testing the Add function, which takes two integers and returns their sum. We call Add with the arguments `2` and `3`, and then compare the result to the expected value of `5`. If the result is not equal to `5`, we use the `t.Errorf` function to report an error.

## Running Unit Tests in Go

To run all the unit tests in a package, you can use the `go test` command followed by the package name. For example, to run all the tests in the `math` package, you would enter the following command:

```shell
$ go test math
```

This will run all the unit tests defined in the `math` package and display the results. By default, `go test` prints a summary of the test results, indicating how many tests passed and how many failed. You can also use the `-v` flag to get more detailed output, including the name of each test function and its result.

If you want to run a specific test function, you can use the `-run` flag followed by a regular expression that matches the name of the test function. For example, to run the `TestAdd` function in the math package, you would enter the following command:

```shell
$ go test -run TestAdd math
```

This will run only the `TestAdd` function in the math package and display the results.

## Analyzing Test Coverage in Go

Test coverage is a metric that measures how much of the code is exercised by the unit tests. In Go, you can use the `-cover` flag with the go test command to generate a coverage report.

To generate a coverage report for the math package, you would enter the following command:

```shell
$ go test -cover math
```

This will run all the unit tests in the math package and display the results, including the percentage of code covered by the tests.

Here's an example of a coverage report:

```lua
ok      math    0.001s  coverage: 100.0%

```

Let's dive deeper into writing more complex unit tests with practical examples.

## Practical Examples

### Testing an HTTP handler

In this example, we'll create a simple HTTP server that serves a single endpoint and write a unit test for the endpoint handler. We'll use the built-in `net/http/httptest` package to create a test server and send HTTP requests to it.

```go
package main

import (
	"net/http"
	"net/http/httptest"
	"testing"
)

func TestHandler(t *testing.T) {
	req, err := http.NewRequest("GET", "/", nil)
	if err != nil {
		t.Fatal(err)
	}

	rr := httptest.NewRecorder()
	handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		w.WriteHeader(http.StatusOK)
	})

	handler.ServeHTTP(rr, req)

	if status := rr.Code; status != http.StatusOK {
		t.Errorf("handler returned wrong status code: got %v want %v",
			status, http.StatusOK)
	}

	expected := ""
	if rr.Body.String() != expected {
		t.Errorf("handler returned unexpected body: got %v want %v",
			rr.Body.String(), expected)
	}
}

```

In this example, we create a new HTTP request for the root endpoint `("/")` and use the `httptest.NewRecorder` function to create a new `ResponseRecorder` to capture the response from the server.

We then define a simple HTTP handler function that always returns a status code of `200`. We call this handler function with the `ServeHTTP` method of the `http.Handler` interface, passing in the `ResponseRecorder` and `Request` objects.

Finally, we use the `Code` and `Body` methods of the `ResponseRecorder` object to check the status code and body of the response, and report any errors using the `t.Errorf` function.

### Testing a database function

In this example, we'll create a function that interacts with a database and write a unit test for it. We'll use the built-in `testing/quick` package to generate random input values for the function.

```go
package main

import (
	"database/sql"
	"fmt"
	"testing"
	"testing/quick"
)

func GetUserName(db *sql.DB, id int) (string, error) {
	var name string
	err := db.QueryRow("SELECT name FROM users WHERE id=?", id).Scan(&name)
	if err != nil {
		return "", err
	}
	return name, nil
}

func TestGetUserName(t *testing.T) {
	db, err := sql.Open("mysql", "user:password@/dbname")
	if err != nil {
		t.Fatal(err)
	}
	defer db.Close()

	f := func(id int) bool {
		name, err := GetUserName(db, id)
		if err != nil {
			t.Error(err)
			return false
		}
		if name == "" {
			t.Errorf("empty name for id=%d", id)
			return false
		}
		return true
	}

	if err := quick.Check(f, nil); err != nil {
		t.Error(err)
	}
}

```

In this example, we define a function called `GetUserName` that takes a database connection and an `ID`, and returns the name of the user with that `ID`. We use the `QueryRow` method of the `sql.DB` object to execute a SQL query and retrieve the name from the result.

We also define a unit test function called `TestGetUserName` that uses the `testing/quick` package to generate random input values for the `GetUserName` function. We pass in a function f that takes an `ID` and returns a boolean

-

In conclusion, unit testing in Golang is a critical component of writing robust and reliable software applications. It helps developers ensure that their code is functioning correctly, identify bugs and errors before they cause issues in production, and maintain code quality and stability over time.

In the practical examples discussed, we showed how to test an HTTP handler and a database function, demonstrating how unit testing can be used to validate different aspects of a software system.

Overall, Golang's built-in testing framework provides developers with the tools they need to create comprehensive and effective unit tests quickly and efficiently. By following best practices for unit testing and writing tests that cover all aspects of their code, developers can be confident that their software is of high quality and meets their users' needs.