๐ Effortless Integration Tests with Testcontainers in Golang ๐งช
Sergio Marcial
Posted on September 4, 2023
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
- Download the Windows installer from the official Golang website.
- Run the installer and follow the on-screen instructions.
- 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:
- Download the macOS installer from the official Golang website.
- Open the downloaded package and follow the installation instructions.
- Open Terminal and run
go version
to verify the installation.
Or use homebrew
brew install go
๐ฆ Installing Testcontainers Go Package
Now, let's install the Testcontainers Go package. Open your terminal/command prompt and run:
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:
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:
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:
go test ./test/
For a complete example using Golang
and Postgres
testcontainers, check out this GitHub repository.
๐ Alternatives and Comparisons
While Testcontainers is a powerful tool, it's essential to know about alternatives and when to use them:
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.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
Posted on September 4, 2023
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.