-
Notifications
You must be signed in to change notification settings - Fork 2
Integration Testing
Johnny Boursiquot edited this page Jun 7, 2024
·
1 revision
Consider the following LibraryService
which relies on a live PostgreSQL server connection to operate. We could mock the database connection using something like sqlmock but there are times when we want to test against a real system over the network.
testing/integration/library.go
package integration
import (
"gorm.io/gorm"
)
type Author struct {
gorm.Model
Name string
}
type Book struct {
gorm.Model
Title string
Authors []Author `gorm:"many2many:book_authors;"`
}
type LibraryService struct {
db *gorm.DB
}
func NewLibraryService(db *gorm.DB) *LibraryService {
return &LibraryService{db: db}
}
func (s *LibraryService) CreateBook(book *Book) error {
return s.db.Create(book).Error
}
func (s *LibraryService) GetBook(id uint) (*Book, error) {
var book Book
err := s.db.Preload("Authors").First(&book, id).Error
return &book, err
}
Your options include:
- Have a local server running either directly on your machine
- Have a PostgreSQL docker container running
- Testcontainers
The following example uses the Testcontainers approach.
You'll need to familiarize yourself with the Go Quickstart guide. Once you do, we can dive into the following code which uses a test flag that we toggle whenever we want to run integration tests along with our unit tests.
package integration_test
import (
"context"
"flag"
"log/slog"
"os"
"testing"
"time"
"github.com/idiomat/dodtnyt/testing/integration"
"github.com/stretchr/testify/assert"
"github.com/testcontainers/testcontainers-go"
tpg "github.com/testcontainers/testcontainers-go/modules/postgres"
"github.com/testcontainers/testcontainers-go/wait"
"gorm.io/driver/postgres"
"gorm.io/gorm"
)
var db *gorm.DB
var runIntegrationTests = flag.Bool("integration", false, "run integration tests")
func TestMain(m *testing.M) {
flag.Parse()
if *runIntegrationTests {
ctx := context.Background()
var err error
pgUser := "user"
pgPass := "password"
pgDB := "test"
pgc, err := tpg.RunContainer(ctx,
testcontainers.WithImage("postgres:16-alpine"),
tpg.WithDatabase(pgDB),
tpg.WithUsername(pgUser),
tpg.WithPassword(pgPass),
testcontainers.WithWaitStrategy(
wait.ForLog("database system is ready to accept connections").
WithOccurrence(2).
WithStartupTimeout(5*time.Second)),
)
if err != nil {
slog.Error("failed to start postgres container", "error", err)
os.Exit(1)
}
defer pgc.Terminate(ctx) // nolint:errcheck
dsn, err := pgc.ConnectionString(ctx, "sslmode=disable")
if err != nil {
slog.Error("failed to get connection string", "error", err)
os.Exit(1)
}
db, err = gorm.Open(
postgres.Open(dsn),
&gorm.Config{},
)
if err != nil {
slog.Error("failed to connect to database", "error", err)
os.Exit(1)
}
if err := db.AutoMigrate(&integration.Author{}, &integration.Book{}); err != nil {
slog.Error("failed to migrate database", "error", err)
os.Exit(1)
}
}
// Run the tests
exitCode := m.Run()
os.Exit(exitCode)
}
func TestCreateBook_Integration(t *testing.T) {
if !*runIntegrationTests {
t.Skip("skipping integration test")
}
service := integration.NewLibraryService(db)
book := &integration.Book{
Title: "Meditations",
Authors: []integration.Author{{Name: "Marcus Aurelius"}},
}
err := service.CreateBook(book)
assert.Nil(t, err)
}
func TestGetBook_Integration(t *testing.T) {
if !*runIntegrationTests {
t.Skip("skipping integration test")
}
service := integration.NewLibraryService(db)
book := &integration.Book{
Title: "Meditations",
Authors: []integration.Author{{Name: "Marcus Aurelius"}},
}
err := service.CreateBook(book)
assert.Nil(t, err)
book, err = service.GetBook(book.ID)
assert.Nil(t, err)
assert.Equal(t, "Meditations", book.Title)
assert.Equal(t, "Marcus Aurelius", book.Authors[0].Name)
}
$ go test -v ./testing/integration/... -integration=true
Expect output along the line of the following:
2024/06/07 12:42:28 github.com/testcontainers/testcontainers-go - Connected to docker:
Server Version: 26.1.1
API Version: 1.44
Operating System: Docker Desktop
Total Memory: 7940 MB
Resolved Docker Host: unix:///var/run/docker.sock
Resolved Docker Socket Path: /var/run/docker.sock
Test SessionID: 8b2614dc444bf0cfc92ece05fa3a0b70c5bf7eb2bb676e99e068d44896360b71
Test ProcessID: 9aa41bd3-4801-443d-a1b7-36d7312baf13
2024/06/07 12:42:28 π³ Creating container for image testcontainers/ryuk:0.7.0
2024/06/07 12:42:28 β
Container created: 492604143fa9
2024/06/07 12:42:28 π³ Starting container: 492604143fa9
2024/06/07 12:42:28 β
Container started: 492604143fa9
2024/06/07 12:42:28 π§ Waiting for container id 492604143fa9 image: testcontainers/ryuk:0.7.0. Waiting for: &{Port:8080/tcp timeout:<nil> PollInterval:100ms}
2024/06/07 12:42:28 π Container is ready: 492604143fa9
2024/06/07 12:42:28 π³ Creating container for image postgres:16-alpine
2024/06/07 12:42:28 β
Container created: a521ef3a5991
2024/06/07 12:42:28 π³ Starting container: a521ef3a5991
2024/06/07 12:42:28 β
Container started: a521ef3a5991
2024/06/07 12:42:28 π§ Waiting for container id a521ef3a5991 image: postgres:16-alpine. Waiting for: &{timeout:<nil> deadline:0x1400047ef70 Strategies:[0x14000194780]}
2024/06/07 12:42:29 π Container is ready: a521ef3a5991
=== RUN TestCreateBook_Integration
--- PASS: TestCreateBook_Integration (0.01s)
=== RUN TestGetBook_Integration
--- PASS: TestGetBook_Integration (0.02s)
PASS
ok github.com/idiomat/dodtnyt/testing/integration 2.051s