Published on

Unit Testing in Go

Authors
  • Name
    Twitter

In this blog post, we'll explore how to write unit tests in Go, one of the most popular programming languages for building fast and efficient applications.

Setting Up Your Go Project

Before we dive into writing unit tests, let's set up a basic Go project. Here is the structure of our project:

├── main.go
├── main_test.go
├── go.mod
└── go.sum

Writing Your First Unit Test

Let's start by creating a simple function in main.go that we'll test.

package main

import "fmt"

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

func main() {
    fmt.Println("Hello, World!")
}

Now, let's write a test for the Add function in main_test.go.

package main

import "testing"

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

    if result != expected {
        t.Errorf("expected %d, got %d", expected, result)
    }
}

Running Your Tests

To run your tests, you can use the go test command:

go test

If everything is set up correctly, you should see an output indicating that the test passed.

PASS
ok      example.com/myapp 0.001s

Testing Multiple Cases

It's often useful to test multiple cases with different inputs and expected outputs. You can do this by creating a table-driven test.

func TestAddMultipleCases(t *testing.T) {
    tests := []struct {
        a, b     int
        expected int
    }{
        {1, 1, 2},
        {2, 2, 4},
        {2, 3, 5},
        {5, 5, 10},
    }

    for _, tt := range tests {
        result := Add(tt.a, tt.b)
        if result != tt.expected {
            t.Errorf("Add(%d, %d): expected %d, got %d", tt.a, tt.b, tt.expected, result)
        }
    }
}

Testing for Errors

Sometimes, you want to test functions that return errors. Let's modify our Add function to return an error if the inputs are negative.

package main

import (
    "errors"
    "fmt"
)

func Add(a, b int) (int, error) {
    if a < 0 || b < 0 {
        return 0, errors.New("inputs must be non-negative")
    }
    return a + b, nil
}

func main() {
    fmt.Println("Hello, World!")
}

And the corresponding test:

func TestAddWithError(t *testing.T) {
    result, err := Add(-1, 1)
    if err == nil {
        t.Errorf("expected an error but got none")
    }

    if result != 0 {
        t.Errorf("expected result to be 0 but got %d", result)
    }
}

Mocking Dependencies

When writing unit tests, you might need to mock dependencies such as databases or external services. Go doesn't have a built-in mocking framework, but you can create your own mocks using interfaces.

Let's say we have a UserService that depends on a UserRepository interface.

type UserRepository interface {
    GetUser(id int) (string, error)
}

type UserService struct {
    repo UserRepository
}

func (s *UserService) GetUserName(id int) (string, error) {
    return s.repo.GetUser(id)
}

We can create a mock implementation of UserRepository for testing.

type MockUserRepository struct{}

func (m *MockUserRepository) GetUser(id int) (string, error) {
    if id == 1 {
        return "Alice", nil
    }
    return "", errors.New("user not found")
}

func TestGetUserName(t *testing.T) {
    mockRepo := &MockUserRepository{}
    service := &UserService{repo: mockRepo}

    name, err := service.GetUserName(1)
    if err != nil {
        t.Errorf("expected no error but got %v", err)
    }

    if name != "Alice" {
        t.Errorf("expected 'Alice' but got %s", name)
    }
}

Conclusion

Unit testing is a crucial part of developing robust Go applications. By writing tests for your code, you can ensure that your application behaves as expected and catches bugs early in the development process. We covered the basics of writing and running unit tests in Go, as well as testing multiple cases, handling errors, and mocking dependencies. Happy testing!