🚀 Effortless Integration Tests with Testcontainers in Golang 🧪
Tags: go, testing, testcontainers, integrationtesting
Published: 2023-09-04 16:56:09

Testing is a vital part of software development, ensuring that your applications run as expected in real-world scenarios. However, writing effective integration and functional tests can be challenging, especially when dealing with databases, message brokers, and external services. In this article, we'll explore how to harness the power of Testcontainers to simplify testing in Golang. Whether you're a beginner or an experienced developer, you'll gain valuable insights into writing robust integration and functional tests using Testcontainers.

### 🏁 Setting Up Your Development Environment

Before we dive into code examples, let's ensure your development environment is ready. We'll cover installing Golang on both Windows and macOS.

#### **Installing Golang on Windows**

1. Download the Windows installer from the [official Golang website](https://golang.org/dl/).
2. Run the installer and follow the on-screen instructions.
3. Open Command Prompt and run `go version` to verify the installation.

#### **Installing Golang on macOS**

There are 2 options to install Golang on macOs

Follow wthe official documentation:

1. Download the macOS installer from the [official Golang website](https://golang.org/dl/).
2. Open the downloaded package and follow the installation instructions.
3. Open Terminal and run `go version` to verify the installation.

Or use homebrew

```shell
brew install go
```

### 📦 Installing Testcontainers Go Package

Now, let's install the Testcontainers Go package. Open your terminal/command prompt and run:

```bash
go get github.com/testcontainers/testcontainers-go
```

### 🌐 Setting Up the Project

Let's assume we're working on a Golang application that interacts with a PostgreSQL database and Kafka message broker. We want to write integration and functional tests for these components.

#### **Project Structure**

Here's a typical project structure:

```
myapp/
|-- main.go
|-- database/
|   |-- database.go
|   |-- database_test.go
|-- messaging/
|   |-- kafka.go
|   |-- kafka_test.go
|-- test/
|   |-- integration_test.go
```

In this structure:

- `main.go` contains your application code.
- `database/database.go` handles database interactions.
- `database/database_test.go` handles database unit testing (We are not interested there, for this article).
- `messaging/kafka.go` manages Kafka communication, creating kafka consumers and/or producer.
- `messaging/kafka_test.go` handles kafka unit testing (We are not interested there, for this article).
- `test/` is where we'll write our integration tests.

### 📋 Writing the Test

Let's create an integration test for the PostgreSQL database. In `integration_test.go`, add the following code:

```go
package test

import (
	"context"
	"database/sql"
	"testing"

	"github.com/testcontainers/testcontainers-go"
	"github.com/testcontainers/testcontainers-go/wait"
	_ "github.com/lib/pq" // Import the PostgreSQL driver
)

func TestPostgreSQLIntegration(t *testing.T) {
	ctx := context.Background()

	// Define a PostgreSQL container
	req := testcontainers.ContainerRequest{
		Image:        "postgres:latest",
		ExposedPorts: []string{"5432/tcp"},
		WaitingFor:   wait.ForLog("database system is ready to accept connections"),
	}

	// Create the container
	container, err := testcontainers.GenericContainer(ctx, testcontainers.GenericContainerRequest{
		ContainerRequest: req,
		Started:          true,
	})
	if err != nil {
		t.Fatal(err)
	}
	defer container.Terminate(ctx)

	// Get the PostgreSQL port
	host, port, _ := container.Host(ctx)
	dsn := "user=postgres password=postgres dbname=postgres sslmode=disable host=" + host + " port=" + port

	// Connect to the database
	db, err := sql.Open("postgres", dsn)
	if err != nil {
		t.Fatal(err)
	}
	defer db.Close()

	// Your database test code here

	// Clean up
	if err := container.Terminate(ctx); err != nil {
		t.Fatal(err)
	}
}
```

In this test:

- We define a PostgreSQL container using Testcontainers.
- We start the container and retrieve the host and port for database connection.
- We connect to the database and perform our test operations.
- Finally, we clean up by terminating the container.

This ensures that each test runs in an isolated PostgreSQL container.

### 🚀 Testing Kafka

Similarly, you can create integration tests for Kafka. In `integration_test.go`, add the following code:

```go
package test

import (
	"context"
	"testing"

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

func TestKafkaIntegration(t *testing.T) {
	ctx := context.Background()

	// Define a Kafka container
	req := testcontainers.ContainerRequest{
		Image:        "confluentinc/cp-kafka:latest",
		ExposedPorts: []string{"9092/tcp"},
		WaitingFor:   wait.ForLog("listeners started on advertised listener"),
	}

	// Create the container
	container, err := testcontainers.GenericContainer(ctx, testcontainers.GenericContainerRequest{
		ContainerRequest: req,
		Started:          true,
	})
	if err != nil {
		t.Fatal(err)
	}
	defer container.Terminate(ctx)

	// Your Kafka test code here

	// Clean up
	if err := container.Terminate(ctx); err != nil {
		t.Fatal(err)
	}
}
```

### 🔄 Running the Tests

You can run your tests using the `go test` command:

```bash
go test ./test/
```

For a complete example using `Golang` and `Postgres` testcontainers, check out this [GitHub repository](https://github.com/sergiommarcial/testcontainers-golang).

### 🔄 Alternatives and Comparisons

While Testcontainers is a powerful tool, it's essential to know about alternatives and when to use them:

1. **Mocking**: For unit tests, you might opt for mocking libraries like `github.com/stretchr/testify/mock` to simulate database and Kafka interactions. This approach is suitable for unit tests but lacks the realism of actual containers.

2. **Docker Compose**: If you prefer using Docker Compose for your tests, it provides more flexibility but requires additional setup and maintenance.

### 💡 Conclusion

Testcontainers in Golang opens the door to efficient integration and functional testing. It allows you to create and manage containers effortlessly, ensuring that your tests reflect real-world scenarios. With Testcontainers, you can write robust, reliable tests for your Golang applications.

Happy testing! 🚀🐳

### 📚 Further Reading

- [Testcontainers Go Documentation](https://github.com/testcontainers/testcontainers-go)