Todo: testing
Who am I?
Damiano Petrungaro
Who am I?
Staff Engineer @Odin
Who am I?
Milan, Italy
Who am I?
Manga and anime
Database and testcontainers
Database and testcontainers
package user
import (
"context"
"errors"
"github.com/google/uuid"
)
var (
ErrNotFound = errors.New("user not found")
ErrNotAdded = errors.New("user not added")
ErrNotUpdated = errors.New("user not updated")
ErrNotRemoved = errors.New("user not removed")
)
type User struct {
ID uuid.UUID
Name string
Email string
}
type Repo interface {
Get(context.Context, uuid.UUID) (*User, error)
Add(context.Context, *User) error
Update(context.Context, *User) error
Remove(context.Context, uuid.UUID) error
}
Database and testcontainers
package user
import (
"context"
"errors"
"github.com/google/uuid"
)
var (
ErrNotFound = errors.New("user not found")
ErrNotAdded = errors.New("user not added")
ErrNotUpdated = errors.New("user not updated")
ErrNotRemoved = errors.New("user not removed")
)
type User struct {
ID uuid.UUID
Name string
Email string
}
type Repo interface {
Get(context.Context, uuid.UUID) (*User, error)
Add(context.Context, *User) error
Update(context.Context, *User) error
Remove(context.Context, uuid.UUID) error
}
Database and testcontainers
package postgres_test
import (
"context"
"database/sql"
"fmt"
"log"
"os"
"testing"
"time"
"github.com/testcontainers/testcontainers-go"
"github.com/testcontainers/testcontainers-go/wait"
)
var db *sql.DB
func TestMain(m *testing.M) {
close := setupDB()
defer close()
os.Exit(m.Run())
}
func setupDB() func() {
ctx := context.Background()
container, err := testcontainers.GenericContainer(
ctx,
testcontainers.GenericContainerRequest{
ContainerRequest: testcontainers.ContainerRequest{
FromDockerfile: testcontainers.FromDockerfile{
Context: "./../../../",
Dockerfile: "build/package/postgres.Dockerfile",
},
ExposedPorts: []string{"5432/tcp"},
WaitingFor: (&wait.LogStrategy{
Log: "database system is ready to accept connections",
Occurrence: 2,
PollInterval: 100 * time.Millisecond,
}).WithStartupTimeout(2 * time.Minute),
Env: map[string]string{
"POSTGRES_DB": "postgres",
"POSTGRES_PASSWORD": "postgres",
"POSTGRES_USER": "postgres",
},
},
Started: true,
Logger: log.Default(),
},
)
if err != nil {
log.Fatalf("could not connect to the database: %s", err)
}
host, err := container.Host(ctx)
if err != nil {
log.Fatalf("could not get container host: %s", err)
}
port, err := container.MappedPort(ctx, "5432")
if err != nil {
log.Fatalf("could not get container port: %s", err)
}
db, err = sql.Open("postgres", fmt.Sprintf("postgres://postgres:postgres@%v:%v/postgres?sslmode=disable", host, port.Port()))
if err != nil {
log.Fatalf("Failed to connect to the database: %v", err)
}
return func() {
if err := db.Close(); err != nil {
log.Fatalf("Failed to close the database connection: %v", err)
}
}
}
Database and testcontainers
package postgres_test
import (
"context"
"errors"
"testing"
"github.com/google/uuid"
"testing/pkg/user"
"testing/pkg/user/postgres"
)
func TestRepository_Get(t *testing.T) {
t.Parallel()
ctx := context.Background()
repo := postgres.New(db)
t.Run("exists", func(t *testing.T) {
given := &user.User{
ID: uuid.New(),
Name: "Luke Skywalker",
Email: "Luke_Skywalker@gmail.com",
}
if _, err := db.ExecContext(
ctx,
"INSERT INTO users (id, name, email) VALUES ($1, $2, $3)",
given.ID,
given.Name,
given.Email,
); err != nil {
t.Fatalf("could not insert user: %s", err)
}
got, err := repo.Get(ctx, given.ID)
if err != nil {
t.Fatalf("could not get user: %s", err)
}
match(t, got, given)
})
t.Run("not exists", func(t *testing.T) {
id := uuid.New()
got, err := repo.Get(ctx, id)
if !errors.Is(err, user.ErrNotFound) {
t.Fatalf("expected error: %s", err)
}
if got != nil {
t.Fatalf("expected nil user, got %v", got)
}
})
}
func TestRepository_Add(t *testing.T) {
t.Parallel()
ctx := context.Background()
repo := postgres.New(db)
given := &user.User{
ID: uuid.New(),
Name: "John Doe",
Email: "johndoe@gmail.com",
}
if err := repo.Add(ctx, given); err != nil {
t.Fatalf("could not add user: %s", err)
}
got := &user.User{}
if err := db.QueryRowContext(
ctx,
"SELECT * FROM users WHERE ID = $1",
given.ID.String(),
).Scan(&got.ID, &got.Name, &got.Email); err != nil {
t.Fatalf("could not query user: %s", err)
}
match(t, got, given)
}
func TestRepository_Update(t *testing.T) {
t.Parallel()
ctx := context.Background()
repo := postgres.New(db)
t.Run("not exists", func(t *testing.T) {
given := &user.User{
ID: uuid.New(),
Name: "Fred Flintstone",
Email: "flintstone@gmail.com",
}
if err := repo.Update(ctx, given); !errors.Is(err, user.ErrNotUpdated) {
t.Fatalf("expected error: %s", err)
}
})
t.Run("exists", func(t *testing.T) {
given := &user.User{
ID: uuid.New(),
Name: "Rich Hickey",
Email: "rich@hickey.com",
}
if _, err := db.ExecContext(
ctx,
"INSERT INTO users (id, name, email) VALUES ($1, $2, $3)",
given.ID,
given.Name,
given.Email,
); err != nil {
t.Fatalf("could not insert user: %s", err)
}
given.Name = "Rich Hickey Jr."
given.Email = "richjr@hickey.com"
if err := repo.Update(ctx, given); err != nil {
t.Fatalf("could not update user: %s", err)
}
var id, name, email string
err := db.QueryRowContext(ctx, "SELECT * FROM users WHERE id = $1", given.ID).Scan(
&id,
&name,
&email,
)
if err != nil {
t.Fatalf("could not query user: %s", err)
}
if id != given.ID.String() {
t.Errorf("expected ID %s, got %s", given.ID.String(), id)
}
if name != given.Name {
t.Errorf("expected Name %s, got %s", given.Name, name)
}
if email != given.Email {
t.Errorf("expected Email %s, got %s", given.Email, email)
}
})
}
func TestRepository_Remove(t *testing.T) {
t.Parallel()
ctx := context.Background()
repo := postgres.New(db)
t.Run("not exists", func(t *testing.T) {
id := uuid.New()
if err := repo.Remove(ctx, id); !errors.Is(err, user.ErrNotRemoved) {
t.Fatalf("expected error: %s", err)
}
})
t.Run("exists", func(t *testing.T) {
given := &user.User{
ID: uuid.New(),
Name: "Johnathan Seagull",
Email: "seagull@gmail.com",
}
if _, err := db.ExecContext(
ctx,
"INSERT INTO users (id, name, email) VALUES ($1, $2, $3)",
given.ID,
given.Name,
given.Email,
); err != nil {
t.Fatalf("could not insert user: %s", err)
}
if err := repo.Remove(ctx, given.ID); err != nil {
t.Fatalf("could not remove user: %s", err)
}
var count int
err := db.QueryRowContext(ctx, "SELECT COUNT(*) FROM users WHERE id = $1", given.ID).Scan(&count)
if err != nil {
t.Fatalf("could not query user: %s", err)
}
if count != 0 {
t.Errorf("expected count 0, got %d", count)
}
})
}
func match(t *testing.T, got, want *user.User) {
t.Helper()
if got.ID != want.ID {
t.Errorf("expected ID %s, got %s", want.ID, got.ID)
}
if got.Name != want.Name {
t.Errorf("expected Name %s, got %s", want.Name, got.Name)
}
if got.Email != want.Email {
t.Errorf("expected Email %s, got %s", want.Email, got.Email)
}
}
Recording HTTP&gRPC
Recording HTTP&gRPC
BDD
BDD
Beyond the Basics: Elevate Your Go Testing Game
By Damiano Petrungaro
Beyond the Basics: Elevate Your Go Testing Game
Todo: testing
- 259