mirror of
https://github.com/xorcare/testing-go-code-with-postgres.git
synced 2024-12-24 16:28:34 +02:00
Publish an example testing go code with Postgres
This commit is contained in:
parent
7e6770223e
commit
b346272f7f
16
.editorconfig
Normal file
16
.editorconfig
Normal file
@ -0,0 +1,16 @@
|
||||
root = true
|
||||
|
||||
[*]
|
||||
charset = utf-8
|
||||
end_of_line = lf
|
||||
indent_size = 2
|
||||
indent_style = space
|
||||
insert_final_newline = true
|
||||
max_line_length = 100
|
||||
tab_width = 4
|
||||
|
||||
[Makefile]
|
||||
indent_style = tab
|
||||
|
||||
[*.go]
|
||||
indent_style = tab
|
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
/coverage.out
|
52
Makefile
Normal file
52
Makefile
Normal file
@ -0,0 +1,52 @@
|
||||
.DEFAULT_GOAL := help
|
||||
|
||||
COVER_FILE ?= coverage.out
|
||||
|
||||
.PHONY: build
|
||||
build: ## Build a command to quickly check compiles.
|
||||
@go build ./...
|
||||
|
||||
.PHONY: check
|
||||
check: lint build test ## Runs all necessary code checks.
|
||||
|
||||
.PHONY: test
|
||||
test: ## Run all tests.
|
||||
@go test -race -count=1 -coverprofile=$(COVER_FILE) ./...
|
||||
@go tool cover -func=$(COVER_FILE) | grep ^total | tr -s '\t'
|
||||
|
||||
.PHONY: test-short
|
||||
test-short: ## Run only unit tests, tests without I/O dependencies.
|
||||
@go test -short ./...
|
||||
|
||||
.PHONY: test-env-up
|
||||
test-env-up: ## Run test environment.
|
||||
@docker-compose up migrate
|
||||
|
||||
.PHONY: test-env-down
|
||||
test-env-down: ## Down and cleanup test environment.
|
||||
@docker-compose down -v
|
||||
|
||||
.PHONY: lint
|
||||
lint: tools ## Check the project with lint.
|
||||
@golangci-lint run \
|
||||
--fix \
|
||||
--disable-all \
|
||||
-E errcheck \
|
||||
-E godot \
|
||||
-E goimports \
|
||||
-E gosimple \
|
||||
-E govet \
|
||||
-E ineffassign \
|
||||
-E misspell \
|
||||
-E staticcheck \
|
||||
-E typecheck \
|
||||
-E unused \
|
||||
-E whitespace \
|
||||
./...
|
||||
|
||||
tools: ## Install all needed tools, e.g.
|
||||
@go install -v github.com/golangci/golangci-lint/cmd/golangci-lint@v1.53.2
|
||||
|
||||
.PHONY: help
|
||||
help: ## Show help for each of the Makefile targets.
|
||||
@grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}'
|
13
README.md
Normal file
13
README.md
Normal file
@ -0,0 +1,13 @@
|
||||
# Example of testing Go code with Postgres
|
||||
|
||||
The example suggests a solution to the problem of cleaning the database after
|
||||
running tests and the problem of running tests in parallel. It also shows how
|
||||
to organize integration testing of Go code with Postgres.
|
||||
|
||||
## How to use
|
||||
|
||||
Run `make test-env-up test` and then everything will happen by itself.
|
||||
|
||||
## Disclaimer
|
||||
|
||||
**This example is not an example of software architecture!**
|
30
docker-compose.yml
Normal file
30
docker-compose.yml
Normal file
@ -0,0 +1,30 @@
|
||||
version: "3.8"
|
||||
|
||||
services:
|
||||
postgres:
|
||||
image: postgres:15.3-alpine3.18
|
||||
environment:
|
||||
POSTGRES_DB: postgres
|
||||
POSTGRES_PASSWORD: postgres
|
||||
POSTGRES_USER: postgres
|
||||
POSTGRES_MULTIPLE_DATABASES: reference
|
||||
healthcheck:
|
||||
test: pg_isready --username "postgres" --dbname "reference"
|
||||
interval: 1s
|
||||
retries: 5
|
||||
timeout: 5s
|
||||
ports:
|
||||
- "32260:5432"
|
||||
volumes:
|
||||
- ./docker-multiple-databases.sh:/docker-entrypoint-initdb.d/docker-multiple-databases.sh:ro
|
||||
|
||||
migrate:
|
||||
image: migrate/migrate:v4.16.2
|
||||
command: >
|
||||
-source 'file:///migrations'
|
||||
-database 'postgresql://postgres:postgres@postgres:5432/reference?sslmode=disable' up
|
||||
depends_on:
|
||||
postgres:
|
||||
condition: service_healthy
|
||||
volumes:
|
||||
- ./migrations:/migrations:ro
|
20
docker-multiple-databases.sh
Executable file
20
docker-multiple-databases.sh
Executable file
@ -0,0 +1,20 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -e
|
||||
set -u
|
||||
|
||||
function create_user_and_database() {
|
||||
local database=$1
|
||||
echo " Creating user and database '$database'"
|
||||
psql -v ON_ERROR_STOP=1 --username "$POSTGRES_USER" <<-EOSQL
|
||||
CREATE DATABASE $database OWNER $POSTGRES_USER;
|
||||
EOSQL
|
||||
}
|
||||
|
||||
if [ -n "$POSTGRES_MULTIPLE_DATABASES" ]; then
|
||||
echo "Multiple database creation requested: $POSTGRES_MULTIPLE_DATABASES"
|
||||
for database in $(echo "$POSTGRES_MULTIPLE_DATABASES" | tr ',' ' '); do
|
||||
create_user_and_database "$database"
|
||||
done
|
||||
echo "Multiple databases created"
|
||||
fi
|
23
go.mod
Normal file
23
go.mod
Normal file
@ -0,0 +1,23 @@
|
||||
module github.com/xorcare/testing-go-code-with-postgres
|
||||
|
||||
go 1.20
|
||||
|
||||
require (
|
||||
github.com/google/uuid v1.3.0
|
||||
github.com/jackc/pgx/v5 v5.3.1
|
||||
github.com/stretchr/testify v1.8.4
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/jackc/pgpassfile v1.0.0 // indirect
|
||||
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect
|
||||
github.com/jackc/puddle/v2 v2.2.0 // indirect
|
||||
github.com/kr/text v0.2.0 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/rogpeppe/go-internal v1.10.0 // indirect
|
||||
golang.org/x/crypto v0.9.0 // indirect
|
||||
golang.org/x/sync v0.1.0 // indirect
|
||||
golang.org/x/text v0.9.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
)
|
37
go.sum
Normal file
37
go.sum
Normal file
@ -0,0 +1,37 @@
|
||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
|
||||
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
|
||||
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
|
||||
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a h1:bbPeKD0xmW/Y25WS6cokEszi5g+S0QxI/d45PkRi7Nk=
|
||||
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
|
||||
github.com/jackc/pgx/v5 v5.3.1 h1:Fcr8QJ1ZeLi5zsPZqQeUZhNhxfkkKBOgJuYkJHoBOtU=
|
||||
github.com/jackc/pgx/v5 v5.3.1/go.mod h1:t3JDKnCBlYIc0ewLF0Q7B8MXmoIaBOZj/ic7iHozM/8=
|
||||
github.com/jackc/puddle/v2 v2.2.0 h1:RdcDk92EJBuBS55nQMMYFXTxwstHug4jkhT5pq8VxPk=
|
||||
github.com/jackc/puddle/v2 v2.2.0/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
|
||||
github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
|
||||
github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
|
||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||
golang.org/x/crypto v0.9.0 h1:LF6fAI+IutBocDJ2OT0Q1g8plpYljMZ4+lty+dsqw3g=
|
||||
golang.org/x/crypto v0.9.0/go.mod h1:yrmDGqONDYtNj3tH8X9dzUun2m2lzPa9ngI6/RUPGR0=
|
||||
golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o=
|
||||
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE=
|
||||
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
1
migrations/000001_create_users_table.down.sql
Normal file
1
migrations/000001_create_users_table.down.sql
Normal file
@ -0,0 +1 @@
|
||||
DROP TABLE users;
|
6
migrations/000001_create_users_table.up.sql
Normal file
6
migrations/000001_create_users_table.up.sql
Normal file
@ -0,0 +1,6 @@
|
||||
CREATE TABLE users
|
||||
(
|
||||
user_id uuid,
|
||||
username VARCHAR(50) UNIQUE NOT NULL,
|
||||
created_at TIMESTAMP WITH TIME ZONE NOT NULL
|
||||
);
|
116
testingpg/testingpg.go
Normal file
116
testingpg/testingpg.go
Normal file
@ -0,0 +1,116 @@
|
||||
// Copyright (c) 2023 Vasiliy Vasilyuk. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package testingpg
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/jackc/pgx/v5/pgxpool"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
type TestingT interface {
|
||||
require.TestingT
|
||||
|
||||
Logf(format string, args ...any)
|
||||
Cleanup(f func())
|
||||
}
|
||||
|
||||
func New(t TestingT) *Postgres {
|
||||
return newPostgres(t).cloneFromReference(t)
|
||||
}
|
||||
|
||||
type Postgres struct {
|
||||
url string
|
||||
ref string
|
||||
|
||||
conn *pgxpool.Pool
|
||||
}
|
||||
|
||||
func newPostgres(t TestingT) *Postgres {
|
||||
urlStr := os.Getenv("TESTING_DB_URL")
|
||||
if urlStr == "" {
|
||||
urlStr = "postgresql://postgres:postgres@localhost:32260/postgres?sslmode=disable"
|
||||
const format = "env TESTING_DB_URL is empty, used default value: %s"
|
||||
t.Logf(format, urlStr)
|
||||
}
|
||||
|
||||
refDatabase := os.Getenv("TESTING_DB_REF")
|
||||
if refDatabase == "" {
|
||||
refDatabase = "reference"
|
||||
}
|
||||
|
||||
pool, err := pgxpool.New(context.Background(), urlStr)
|
||||
require.NoError(t, err)
|
||||
|
||||
return &Postgres{
|
||||
url: urlStr,
|
||||
ref: refDatabase,
|
||||
|
||||
conn: pool,
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Postgres) URL() string {
|
||||
return p.url
|
||||
}
|
||||
|
||||
func (p *Postgres) PgxPool() *pgxpool.Pool {
|
||||
return p.conn
|
||||
}
|
||||
|
||||
func (p *Postgres) cloneFromReference(t TestingT) *Postgres {
|
||||
cfg, err := pgxpool.ParseConfig(p.url)
|
||||
require.NoError(t, err)
|
||||
|
||||
pool, err := pgxpool.New(context.Background(), p.url)
|
||||
require.NoError(t, err)
|
||||
|
||||
newDatabaseName := uuid.New().String()
|
||||
|
||||
const sqlTemplate = `CREATE DATABASE %q WITH TEMPLATE %s OWNER %s;`
|
||||
sql := fmt.Sprintf(
|
||||
sqlTemplate,
|
||||
newDatabaseName,
|
||||
p.ref,
|
||||
cfg.ConnConfig.User,
|
||||
)
|
||||
_, err = pool.Exec(context.Background(), sql)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Automatically drop database copy after the test is completed.
|
||||
t.Cleanup(func() {
|
||||
sql := fmt.Sprintf(`DROP DATABASE %q WITH (FORCE);`, newDatabaseName)
|
||||
|
||||
ctx, done := context.WithTimeout(context.Background(), time.Minute)
|
||||
defer done()
|
||||
|
||||
_, err := p.conn.Exec(ctx, sql)
|
||||
require.NoError(t, err)
|
||||
})
|
||||
|
||||
urlString := replaceDBName(t, cfg, newDatabaseName)
|
||||
newPool, err := pgxpool.New(context.Background(), urlString)
|
||||
require.NoError(t, err)
|
||||
|
||||
return &Postgres{
|
||||
url: urlString,
|
||||
ref: newDatabaseName,
|
||||
|
||||
conn: newPool,
|
||||
}
|
||||
}
|
||||
|
||||
func replaceDBName(t TestingT, cfg *pgxpool.Config, dbname string) string {
|
||||
r, err := url.Parse(cfg.ConnString())
|
||||
require.NoError(t, err)
|
||||
r.Path = dbname
|
||||
return r.String()
|
||||
}
|
95
testingpg/testingpg_test.go
Normal file
95
testingpg/testingpg_test.go
Normal file
@ -0,0 +1,95 @@
|
||||
// Copyright (c) 2023 Vasiliy Vasilyuk. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package testingpg_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/jackc/pgx/v5/pgxpool"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/xorcare/testing-go-code-with-postgres/testingpg"
|
||||
)
|
||||
|
||||
func TestNewPostgres(t *testing.T) {
|
||||
if testing.Short() {
|
||||
t.Skip("skipping test in short mode")
|
||||
}
|
||||
|
||||
t.Parallel()
|
||||
|
||||
t.Run("Successfully connect by URL and get version", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
// Arrange
|
||||
postgres := testingpg.New(t)
|
||||
|
||||
ctx := context.Background()
|
||||
dbPool, err := pgxpool.New(ctx, postgres.URL())
|
||||
require.NoError(t, err)
|
||||
|
||||
// Act
|
||||
var version string
|
||||
err = dbPool.QueryRow(ctx, "SELECT version();").Scan(&version)
|
||||
|
||||
// Assert
|
||||
require.NoError(t, err)
|
||||
require.NotEmpty(t, version)
|
||||
t.Log(version)
|
||||
})
|
||||
|
||||
t.Run("Successfully obtained a version using a pre-configured conn", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
// Arrange
|
||||
postgres := testingpg.New(t)
|
||||
ctx := context.Background()
|
||||
|
||||
// Act
|
||||
var version string
|
||||
err := postgres.PgxPool().QueryRow(ctx, "SELECT version();").Scan(&version)
|
||||
|
||||
// Assert
|
||||
require.NoError(t, err)
|
||||
require.NotEmpty(t, version)
|
||||
|
||||
t.Log(version)
|
||||
})
|
||||
|
||||
t.Run("Changes are not visible in different instances", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
// Arrange
|
||||
postgres1 := testingpg.New(t)
|
||||
postgres2 := testingpg.New(t)
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
// Act
|
||||
const sql = `CREATE TABLE "no_conflict" (id integer PRIMARY KEY)`
|
||||
_, err1 := postgres1.PgxPool().Exec(ctx, sql)
|
||||
_, err2 := postgres2.PgxPool().Exec(ctx, sql)
|
||||
|
||||
// Assert
|
||||
require.NoError(t, err1)
|
||||
require.NoError(t, err2, "databases must be isolated for each instance")
|
||||
})
|
||||
|
||||
t.Run("URL is different at different instances", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
// Arrange
|
||||
postgres1 := testingpg.New(t)
|
||||
postgres2 := testingpg.New(t)
|
||||
|
||||
// Act
|
||||
url1 := postgres1.URL()
|
||||
url2 := postgres2.URL()
|
||||
|
||||
// Assert
|
||||
require.NotEqual(t, url1, url2)
|
||||
})
|
||||
}
|
17
user_model.go
Normal file
17
user_model.go
Normal file
@ -0,0 +1,17 @@
|
||||
// Copyright (c) 2023 Vasiliy Vasilyuk. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package testing_go_code_with_postgres
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
type User struct {
|
||||
ID uuid.UUID
|
||||
Username string
|
||||
CreatedAt time.Time
|
||||
}
|
55
user_repository.go
Normal file
55
user_repository.go
Normal file
@ -0,0 +1,55 @@
|
||||
// Copyright (c) 2023 Vasiliy Vasilyuk. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package testing_go_code_with_postgres
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/jackc/pgx/v5/pgxpool"
|
||||
)
|
||||
|
||||
func NewUserRepository(db *pgxpool.Pool) *UserRepository {
|
||||
return &UserRepository{db: db}
|
||||
}
|
||||
|
||||
type UserRepository struct {
|
||||
db *pgxpool.Pool
|
||||
}
|
||||
|
||||
func (r *UserRepository) ReadUser(ctx context.Context, userID uuid.UUID) (User, error) {
|
||||
const sql = `SELECT user_id, username, created_at FROM users WHERE user_id = $1;`
|
||||
|
||||
user := User{}
|
||||
|
||||
row := r.db.QueryRow(ctx, sql, userID)
|
||||
err := row.Scan(&user.ID, &user.Username, &user.CreatedAt)
|
||||
if err != nil {
|
||||
const format = "failed selection of User from database: %v"
|
||||
return User{}, fmt.Errorf(format, err)
|
||||
}
|
||||
|
||||
return user, nil
|
||||
}
|
||||
|
||||
func (r *UserRepository) CreateUser(ctx context.Context, user User) error {
|
||||
const sql = `INSERT INTO users (user_id, username, created_at) VALUES ($1,$2,$3);`
|
||||
|
||||
_, err := r.db.Exec(
|
||||
ctx,
|
||||
sql,
|
||||
user.ID,
|
||||
user.Username,
|
||||
user.CreatedAt,
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
const format = "failed insertion of User to database: %v"
|
||||
return fmt.Errorf(format, err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
77
user_repository_test.go
Normal file
77
user_repository_test.go
Normal file
@ -0,0 +1,77 @@
|
||||
// Copyright (c) 2023 Vasiliy Vasilyuk. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package testing_go_code_with_postgres
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/xorcare/testing-go-code-with-postgres/testingpg"
|
||||
)
|
||||
|
||||
func TestUserRepository_CreateUser(t *testing.T) {
|
||||
newFullyFiledUser := func() User {
|
||||
return User{
|
||||
ID: uuid.New(),
|
||||
Username: "gopher",
|
||||
CreatedAt: time.Now().Truncate(time.Microsecond),
|
||||
}
|
||||
}
|
||||
|
||||
t.Run("Successfully created a User", func(t *testing.T) {
|
||||
// Arrange
|
||||
postgres := testingpg.New(t)
|
||||
repo := NewUserRepository(postgres.PgxPool())
|
||||
|
||||
user := newFullyFiledUser()
|
||||
|
||||
// Act
|
||||
err := repo.CreateUser(context.Background(), user)
|
||||
|
||||
// Assert
|
||||
require.NoError(t, err)
|
||||
|
||||
gotUser, err := repo.ReadUser(context.Background(), user.ID)
|
||||
require.NoError(t, err)
|
||||
|
||||
require.Equal(t, user, gotUser)
|
||||
})
|
||||
|
||||
t.Run("Cannot create a user with the same ID", func(t *testing.T) {
|
||||
// Arrange
|
||||
postgres := testingpg.New(t)
|
||||
repo := NewUserRepository(postgres.PgxPool())
|
||||
|
||||
user := newFullyFiledUser()
|
||||
|
||||
err := repo.CreateUser(context.Background(), user)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Act
|
||||
err = repo.CreateUser(context.Background(), user)
|
||||
|
||||
// Assert
|
||||
require.Error(t, err)
|
||||
require.Contains(t, err.Error(), "duplicate key value violates unique constraint")
|
||||
})
|
||||
}
|
||||
|
||||
func TestUserRepository_ReadUser(t *testing.T) {
|
||||
t.Run("Get an error if the user does not exist", func(t *testing.T) {
|
||||
// Arrange
|
||||
postgres := testingpg.New(t)
|
||||
repo := NewUserRepository(postgres.PgxPool())
|
||||
|
||||
// Act
|
||||
_, err := repo.ReadUser(context.Background(), uuid.New())
|
||||
|
||||
// Assert
|
||||
require.Error(t, err)
|
||||
})
|
||||
}
|
Loading…
Reference in New Issue
Block a user