-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Incorporate use of Testcontainers for unit testing
Includes multiple examples for use in meetup talk, https://www.meetup.com/stl-go/events/298426035/ Signed-off-by: Paul Balogh <[email protected]>
- Loading branch information
Showing
6 changed files
with
562 additions
and
7 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,142 @@ | ||
package db | ||
|
||
import ( | ||
"context" | ||
"log" | ||
"testing" | ||
|
||
"github.com/stretchr/testify/assert" | ||
"github.com/stretchr/testify/suite" | ||
|
||
"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. | ||
|
||
// 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.Greater(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") | ||
} | ||
} | ||
} | ||
|
||
// TestDatabase_TestSuite executes the suite of tests. | ||
func TestDatabase_TestSuite(t *testing.T) { | ||
t.Parallel() | ||
suite.Run(t, new(PlaceTestSuite)) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,140 @@ | ||
package db | ||
|
||
import ( | ||
"context" | ||
"log" | ||
"testing" | ||
|
||
"github.com/weesvc/weesvc-gorilla/model" | ||
"github.com/weesvc/weesvc-gorilla/testhelpers" | ||
|
||
"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) | ||
|
||
places, err := placeDb.GetPlaces() | ||
assert.NoError(t, err) | ||
assert.Equal(t, 10, len(places)) | ||
} | ||
|
||
func TestDatabase_GetPlaceByID(t *testing.T) { | ||
t.Parallel() | ||
placeDb := setupDatabase(t) | ||
|
||
fetchId := uint(6) | ||
place, err := placeDb.GetPlaceByID(fetchId) | ||
if assert.NoError(t, err) { | ||
assert.Equal(t, fetchId, place.ID) | ||
assert.Equal(t, "MIA", place.Name) | ||
assert.Equal(t, "Miami International Airport, FL, USA", place.Description) | ||
assert.Equal(t, 25.79516, place.Latitude) | ||
assert.Equal(t, -80.27959, place.Longitude) | ||
assert.NotNil(t, place.CreatedAt) | ||
assert.NotNil(t, place.UpdatedAt) | ||
} | ||
} | ||
|
||
func TestDatabase_CreatePlace(t *testing.T) { | ||
t.Parallel() | ||
placeDb := setupDatabase(t) | ||
|
||
newPlace := &model.Place{ | ||
ID: 20, | ||
Name: "Kerid Crater", | ||
Description: "Kerid Crater, Iceland", | ||
Latitude: 64.04126, | ||
Longitude: -20.88530, | ||
} | ||
err := placeDb.CreatePlace(newPlace) | ||
if assert.NoError(t, err) { | ||
// Verify our inserted place | ||
created, err := placeDb.GetPlaceByID(newPlace.ID) | ||
if assert.NoError(t, err) { | ||
assert.Equal(t, newPlace.ID, created.ID) | ||
assert.Equal(t, newPlace.Name, created.Name) | ||
assert.Equal(t, newPlace.Description, created.Description) | ||
assert.Equal(t, newPlace.Latitude, created.Latitude) | ||
assert.Equal(t, newPlace.Longitude, created.Longitude) | ||
assert.NotNil(t, created.CreatedAt) | ||
assert.NotNil(t, created.UpdatedAt) | ||
} | ||
} | ||
} | ||
|
||
func TestDatabase_UpdatePlace(t *testing.T) { | ||
t.Parallel() | ||
placeDb := setupDatabase(t) | ||
|
||
original, err := placeDb.GetPlaceByID(7) | ||
if assert.NoError(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(t, placeDb.UpdatePlace(changes)) { | ||
// Verify the updated place | ||
updated, err := placeDb.GetPlaceByID(original.ID) | ||
if assert.NoError(t, err) { | ||
assert.Equal(t, original.ID, updated.ID) | ||
assert.Equal(t, changes.Name, updated.Name) | ||
assert.Equal(t, changes.Description, updated.Description) | ||
assert.Equal(t, changes.Latitude, updated.Latitude) | ||
assert.Equal(t, changes.Longitude, updated.Longitude) | ||
assert.Equal(t, original.CreatedAt, updated.CreatedAt) | ||
assert.Greater(t, original.UpdatedAt, updated.UpdatedAt) | ||
} | ||
} | ||
} | ||
} | ||
|
||
func TestDatabase_DeletePlaceByID(t *testing.T) { | ||
t.Parallel() | ||
placeDb := setupDatabase(t) | ||
|
||
deleteID := uint(1) | ||
_, err := placeDb.GetPlaceByID(deleteID) | ||
if assert.NoError(t, err) { | ||
if assert.NoError(t, placeDb.DeletePlaceByID(deleteID)) { | ||
// Verify no longer retrievable | ||
_, err = placeDb.GetPlaceByID(deleteID) | ||
assert.EqualError(t, err, "unable to get place: record not found") | ||
} | ||
} | ||
} | ||
|
||
func setupDatabase(t *testing.T) *Database { | ||
ctx := context.Background() | ||
|
||
pgContainer, err := testhelpers.CreatePostgresContainer(ctx) | ||
if err != nil { | ||
log.Fatal(err) | ||
} | ||
|
||
placeDb, err := New(&Config{ | ||
DatabaseURI: pgContainer.ConnectionString, | ||
Dialect: "postgres", | ||
Verbose: true, | ||
}) | ||
if err != nil { | ||
log.Fatal(err) | ||
} | ||
|
||
t.Cleanup(func() { | ||
if err := pgContainer.Terminate(ctx); err != nil { | ||
t.Fatalf("failed to terminate pgContainer: %s", err) | ||
} | ||
}) | ||
|
||
return placeDb | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.