DEV Community

Cover image for πŸš€ Effortless Integration Tests with Testcontainers in Golang πŸ§ͺ
Sergio Marcial
Sergio Marcial

Posted on

πŸš€ Effortless Integration Tests with Testcontainers in Golang πŸ§ͺ

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.
  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.
  2. Open the downloaded package and follow the installation instructions.
  3. Open Terminal and run go version to verify the installation.

Or use homebrew

brew install go
Enter fullscreen mode Exit fullscreen mode

πŸ“¦ 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
Enter fullscreen mode Exit fullscreen mode

🌐 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
Enter fullscreen mode Exit fullscreen mode

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)
    }
}
Enter fullscreen mode Exit fullscreen mode

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)
    }
}
Enter fullscreen mode Exit fullscreen mode

πŸ”„ Running the Tests

You can run your tests using the go test command:

go test ./test/
Enter fullscreen mode Exit fullscreen mode

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:

  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

Top comments (0)