Golang by Example

Logo

Testing

Testing is a crucial part of software development, and Go provides built-in support for testing through the testing package. In this section, we will explore how to write and run tests in Go, as well as some best practices for testing your code.

Writing Tests

To write a test in Go, you need to create a new file with the _test.go suffix. Inside this file, you can define test functions that start with the word Test. Each test function should take a pointer to testing.T as its only argument. Here's an example of a simple test:


  // file: adder.go
  package adder

  func Add(a, b int) int {
    return a + b
  }



// file: adder_test.go
  package adder

  import (
    "testing"
  )

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

The TestAdd function tests the Add function from the adder package. It checks if the result of adding 2 and 3 is equal to 5. If the test fails, it uses t.Errorf to report the error.

Running Tests

To run your test, you can use the go test command. This command will automatically find and run all the tests in your package. You can also run tests in a specific file by providing the filename as an argument:


  $ go test -v
  === RUN   TestAdd
  --- PASS: TestAdd (0.00s)
  PASS
  ok      example.com/adder 0.001s

💡 Quiz
Why do we need to use the `-v` flag when running tests?

Assert

There is no built-in assertion library in Go, but there is well-known third-party libraries that provide assertion functions. One of the most popular libraries is stretchr/testify. You can install it using the following command:


  go get github.com/stretchr/testify



  package adder_test

  import (
    "testing"

    "github.com/stretchr/testify/assert"
  )

  func TestAdd(t *testing.T) {
    result := Add(2, 3)
    assert.Equal(t, 5, result)
  }

If you do multiple assertions in a single test, you can use pattern below:


  import (
    "testing"
    "github.com/stretchr/testify/assert"
  )

  func TestSomething(t *testing.T) {
    assert := assert.New(t)

    var a string = "Hello"
    var b string = "Hello"

    assert.Equal(a, b, "The two words should be the same.")
  }

Coverage

Code coverage is a measure of how much of your code is executed during testing. Go provides built-in support for measuring code coverage using the -cover flag with the go test command. Here's how to run tests with coverage:


  $ go test -coverprofile <filename> <package_name>

Test Containers

Test containers are a way to run tests in isolated environments using Docker containers. This is particularly useful for testing code that interacts with external services, such as databases or APIs. The testcontainers-go library provides a simple way to create and manage test containers in Go.

In traditional testing, you might need to setup databases, message queues, or other services before running your tests. This can be cumbersome and time-consuming. Test containers allow you to spin up these services in Docker containers, run your tests, and then tear down the containers afterward.

You can install it using the following command:


  go get github.com/testcontainers/testcontainers-go

Assume you need to test a function that connects to Redis. Then you can use the testcontainers-go library to create a Redis container for your tests. Here's an example:


   // file: cache.go
    package cache

    import (
        "github.com/redis/go-redis/v9"
        "context"
    )

    func GetCache(client *redis.Client, key string) (string, error) {
        ctx := context.Background()
        val, err := client.Get(ctx, key).Result()
        if err != nil {
            return "", err
        }
        return val, nil
    }



    // file cache_test.go
    package cache

    import (
        "context"
        "testing"

        "github.com/stretchr/testify/require"
        "github.com/redis/go-redis/v9"

        "github.com/testcontainers/testcontainers-go"
        "github.com/testcontainers/testcontainers-go/wait"
    )

    func TestCache(t *testing.T) {
        ctx := context.Background()
        req := testcontainers.ContainerRequest{
            Image:        "redis:latest",
            ExposedPorts: []string{"6379/tcp"},
            WaitingFor:   wait.ForLog("Ready to accept connections"),
        }
        redisC, err := testcontainers.GenericContainer(ctx, testcontainers.GenericContainerRequest{
            ContainerRequest: req,
            Started:          true,
        })

        endpoint, err := redisC.Endpoint(ctx, "")
        if err != nil {
            t.Error(err)
        }

        client := redis.NewClient(&redis.Options{
            Addr: endpoint,
        })

        // Set a key in Redis
        err = client.Set(ctx, "key", "value", 0).Err()
        if err != nil {
            t.Error(err)
        }

        // Test the GetCache function
        val, err := GetCache(client, "key")
        require.NoError(t, err)
        require.Equal(t, "value", val)

        // Cleanup
        testcontainers.CleanupContainer(t, redisC)
        require.NoError(t, err)
    }


Table of contents

Hello World - Begin with the classic Hello World program
Primitives - Learn about the basic data types in Go
Flow Control - Controlling the flow of your program
Struct - All about struct
Functions - Define and call functions
Methods and Interfaces - Methods and interfaces in Go
Error Handling - Handling errors idiomatically
Concurrency - Goroutines and channels
Anti Patterns - Anti patterns in Go
Libraries - Standard library and third-party libraries
Testing - Writing unit tests with `go test`
Benchmarking - Performance benchmarks
Containerization - Dockerize your Go app
What's Next? - Explore the next steps in your Golang journey


Share to: