Skip to content

Commit

Permalink
Provide examples of varied testcontainer implementation styles
Browse files Browse the repository at this point in the history
Examples created for StLGo Meetup (https://www.youtube.com/watch?v=sX4s1HqPZcw)

Signed-off-by: Paul Balogh <[email protected]>
  • Loading branch information
javaducky committed Jan 24, 2024
1 parent 6b25c52 commit 1fde32c
Show file tree
Hide file tree
Showing 3 changed files with 261 additions and 0 deletions.
112 changes: 112 additions & 0 deletions db/place_sequential_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
package db

import (
"context"
"path/filepath"
"testing"
"time"

"github.com/weesvc/weesvc-gorilla/model"

"github.com/stretchr/testify/assert"
"github.com/testcontainers/testcontainers-go"
"github.com/testcontainers/testcontainers-go/modules/postgres"
"github.com/testcontainers/testcontainers-go/wait"
)

// Here we're testing each CRUD method sequentially using a single container for the test.
// This is a bit more of a brute force test, but fastest to "market."

// TestDatabase provided as an example where we do ALL THE THINGS in a single testcase.
func TestDatabase(t *testing.T) {
// _Real_ test is provided with `place_test.go`...this is just for learning.
t.Skip()

t.Parallel()
ctx := context.Background()

pgContainer, err := postgres.RunContainer(ctx,
testcontainers.WithImage("postgres:15.3-alpine"),
postgres.WithInitScripts(filepath.Join("..", "testdata", "init-db.sql")),
postgres.WithDatabase("test-db"),
postgres.WithUsername("postgres"),
postgres.WithPassword("postgres"),
testcontainers.WithWaitStrategy(
wait.ForLog("database system is ready to accept connections").
WithOccurrence(2).WithStartupTimeout(5*time.Second)),
)
if err != nil {
t.Fatal(err)
}

t.Cleanup(func() {
if terr := pgContainer.Terminate(ctx); terr != nil {
t.Fatalf("failed to terminate pgContainer: %s", terr)
}
})

connStr, err := pgContainer.ConnectionString(ctx, "sslmode=disable")
if err != nil {
t.Fatal(err)
}

placeDB, err := New(&Config{
DatabaseURI: connStr,
Dialect: "postgres",
Verbose: true,
})
if err != nil {
t.Fatal(err)
}

// Check 1: Retrieve all places
places, err := placeDB.GetPlaces()
if err != nil {
t.Fatal(err)
}
assert.Equal(t, len(places), 10)

// Check 2: Add a new place
newPlace := &model.Place{
ID: 20,
Name: "Kerid Crater",
Description: "Kerid Crater, Iceland",
Latitude: 64.04126,
Longitude: -20.88530,
}
err = placeDB.CreatePlace(newPlace)
if err != nil {
t.Fatal(err)
}

// Check 3: Retrieve the new place
place, err := placeDB.GetPlaceByID(newPlace.ID)
if err != nil {
t.Fatal(err)
}
assert.Equal(t, place.ID, newPlace.ID)
assert.Equal(t, place.Name, newPlace.Name)
assert.Equal(t, place.Description, newPlace.Description)
assert.Equal(t, place.Latitude, newPlace.Latitude)
assert.Equal(t, place.Longitude, newPlace.Longitude)
assert.NotNil(t, place.CreatedAt)
assert.NotNil(t, place.UpdatedAt)

// Check 4: Update a place
updatedDescription := "UPDATED"
err = placeDB.UpdatePlace(&model.Place{ID: newPlace.ID, Description: updatedDescription})
if err != nil {
t.Fatal(err)
}
place, _ = placeDB.GetPlaceByID(newPlace.ID)
assert.Equal(t, updatedDescription, place.Description)
assert.Greater(t, place.UpdatedAt, place.CreatedAt)

// Check 5: Delete the place
err = placeDB.DeletePlaceByID(newPlace.ID)
if err != nil {
t.Fatal(err)
}
_, err = placeDB.GetPlaceByID(newPlace.ID)
assert.EqualError(t, err, "unable to get place: record not found")
}
144 changes: 144 additions & 0 deletions db/place_suite_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
package db

import (
"context"

Check failure on line 4 in db/place_suite_test.go

View workflow job for this annotation

GitHub Actions / lint

File is not `gofumpt`-ed (gofumpt)
"github.com/stretchr/testify/assert"

Check failure on line 5 in db/place_suite_test.go

View workflow job for this annotation

GitHub Actions / lint

File is not `goimports`-ed (goimports)
"github.com/stretchr/testify/suite"
"log"

Check failure on line 7 in db/place_suite_test.go

View workflow job for this annotation

GitHub Actions / lint

File is not `gofumpt`-ed (gofumpt)
"testing"

"github.com/weesvc/weesvc-gorilla/model"
"github.com/weesvc/weesvc-gorilla/testhelpers"
)

// Here we're testing each CRUD method independently, but we're using a single container
// for the entire test suite. This _may_ be ok, but you need to be wary of what is changing
// in the database as suite tests may collide.
//
// With this method, we've refactored some container work into the `testhelpers` package.

// TestDatabase_TestSuite executes the suite of tests.
func TestDatabase_TestSuite(t *testing.T) {
// _Real_ test is provided with `place_test.go`...this is just for learning.
t.Skip()

t.Parallel()
suite.Run(t, new(PlaceTestSuite))
}

// PlaceTestSuite contains shared state amongst all test(s) within the suite.
type PlaceTestSuite struct {
suite.Suite
pgContainer *testhelpers.PostgresContainer
ctx context.Context
placeDB *Database // Reference to our test fixture
}

// SetupSuite executes prior to any test(s) in order to prepare the shared state.
func (suite *PlaceTestSuite) SetupSuite() {
suite.ctx = context.Background()
pgContainer, err := testhelpers.CreatePostgresContainer(suite.ctx)
if err != nil {
log.Fatal(err)
}
suite.pgContainer = pgContainer
placeDB, err := New(&Config{
DatabaseURI: pgContainer.ConnectionString,
Dialect: "postgres",
Verbose: true,
})
if err != nil {
log.Fatal(err)
}
suite.placeDB = placeDB
}

// TearDownSuite executes cleanup after all test(s) have run.
func (suite *PlaceTestSuite) TearDownSuite() {
if err := suite.pgContainer.Terminate(suite.ctx); err != nil {
log.Fatalf("error terminating postgres container: %s", err)
}
}

func (suite *PlaceTestSuite) Test_GetPlaces() {
places, err := suite.placeDB.GetPlaces()
if assert.NoError(suite.T(), err) {
assert.Greater(suite.T(), len(places), 0)
}
}

func (suite *PlaceTestSuite) Test_GetPlaceByID() {
fetchID := uint(6)
place, err := suite.placeDB.GetPlaceByID(fetchID)
if assert.NoError(suite.T(), err) {
assert.Equal(suite.T(), fetchID, place.ID)
assert.Equal(suite.T(), "MIA", place.Name)
assert.Equal(suite.T(), "Miami International Airport, FL, USA", place.Description)
assert.Equal(suite.T(), 25.79516, place.Latitude)
assert.Equal(suite.T(), -80.27959, place.Longitude)
assert.NotNil(suite.T(), place.CreatedAt)
assert.NotNil(suite.T(), place.UpdatedAt)
}
}

func (suite *PlaceTestSuite) Test_CreatePlace() {
newPlace := &model.Place{
ID: 20,
Name: "Kerid Crater",
Description: "Kerid Crater, Iceland",
Latitude: 64.04126,
Longitude: -20.88530,
}
err := suite.placeDB.CreatePlace(newPlace)
if assert.NoError(suite.T(), err) {
// Verify our inserted newPlace
created, err := suite.placeDB.GetPlaceByID(newPlace.ID)
if assert.NoError(suite.T(), err) {
assert.Equal(suite.T(), newPlace.ID, created.ID)
assert.Equal(suite.T(), newPlace.Name, created.Name)
assert.Equal(suite.T(), newPlace.Description, created.Description)
assert.Equal(suite.T(), newPlace.Latitude, created.Latitude)
assert.Equal(suite.T(), newPlace.Longitude, created.Longitude)
assert.NotNil(suite.T(), created.CreatedAt)
assert.NotNil(suite.T(), created.UpdatedAt)
}
}
}

func (suite *PlaceTestSuite) Test_UpdatePlace() {
original, err := suite.placeDB.GetPlaceByID(7)
if assert.NoError(suite.T(), err) {
changes := &model.Place{
ID: original.ID,
Name: "The Alamo",
Description: "The Alamo, San Antonio, TX, USA",
Latitude: 29.42590,
Longitude: -98.48625,
}
if assert.NoError(suite.T(), suite.placeDB.UpdatePlace(changes)) {
// Verify the updated place
updated, err := suite.placeDB.GetPlaceByID(original.ID)
if assert.NoError(suite.T(), err) {
assert.Equal(suite.T(), original.ID, updated.ID)
assert.Equal(suite.T(), changes.Name, updated.Name)
assert.Equal(suite.T(), changes.Description, updated.Description)
assert.Equal(suite.T(), changes.Latitude, updated.Latitude)
assert.Equal(suite.T(), changes.Longitude, updated.Longitude)
assert.Equal(suite.T(), original.CreatedAt, updated.CreatedAt)
assert.NotEqual(suite.T(), original.UpdatedAt, updated.UpdatedAt)
}
}
}
}

func (suite *PlaceTestSuite) Test_DeletePlaceByID() {
deleteID := uint(1)
_, err := suite.placeDB.GetPlaceByID(deleteID)
if assert.NoError(suite.T(), err) {
if assert.NoError(suite.T(), suite.placeDB.DeletePlaceByID(deleteID)) {
// Verify no longer retrievable
_, err = suite.placeDB.GetPlaceByID(deleteID)
assert.EqualError(suite.T(), err, "unable to get place: record not found")
}
}
}
5 changes: 5 additions & 0 deletions db/place_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,11 @@ import (
"github.com/stretchr/testify/assert"
)

// Here we're testing each CRUD method independently, but we're using a fresh container
// for each test. This _may_ be ok, but could be a bit of overhead for CICD pipelines.
//
// With this method, we've refactored some container work into the `testhelpers` package.

func TestDatabase_GetPlaces(t *testing.T) {
t.Parallel()
placeDB := setupDatabase(t)
Expand Down

0 comments on commit 1fde32c

Please sign in to comment.