From e6cf794f98b49a21780c2e3bdadafa27a46a9fda Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Manuel=20de=20la=20Pe=C3=B1a?= Date: Wed, 3 Jul 2024 14:25:29 +0200 Subject: [PATCH 01/21] chore: add testcontainers advanced feature flag --- cmd/flags/advancedFeatures.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/cmd/flags/advancedFeatures.go b/cmd/flags/advancedFeatures.go index 8e8ad899..4140d00e 100644 --- a/cmd/flags/advancedFeatures.go +++ b/cmd/flags/advancedFeatures.go @@ -12,9 +12,10 @@ const ( GoProjectWorkflow string = "githubaction" Websocket string = "websocket" Tailwind string = "tailwind" + Testcontainers string = "testcontainers" ) -var AllowedAdvancedFeatures = []string{string(Htmx), string(GoProjectWorkflow), string(Websocket), string(Tailwind)} +var AllowedAdvancedFeatures = []string{string(Htmx), string(GoProjectWorkflow), string(Websocket), string(Tailwind), string(Testcontainers)} func (f AdvancedFeatures) String() string { return strings.Join(f, ",") From baa7d6a32d337d99ce7020115ebc96725310c756 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Manuel=20de=20la=20Pe=C3=B1a?= Date: Wed, 3 Jul 2024 14:35:55 +0200 Subject: [PATCH 02/21] docs: add new advanced feature to the docs --- README.md | 1 + docs/docs/advanced-flag/advanced-flag.md | 3 +++ docs/mkdocs.yml | 1 + 3 files changed, 5 insertions(+) diff --git a/README.md b/README.md index ea4133e7..ff821474 100644 --- a/README.md +++ b/README.md @@ -112,6 +112,7 @@ You can now use the `--advanced` flag when running the `create` command to get a - CI/CD workflow setup using [Github Actions](https://docs.github.com/en/actions) - [Websocket](https://pkg.go.dev/nhooyr.io/websocket) sets up a websocket endpoint - [Tailwind](https://tailwindcss.com/) css framework +- [Testcontainers](https://testcontainers.com/) sets up integration tests using Testcontainers for Go Note: selecting tailwind option automatically selects htmx. diff --git a/docs/docs/advanced-flag/advanced-flag.md b/docs/docs/advanced-flag/advanced-flag.md index 00511142..c266cea5 100644 --- a/docs/docs/advanced-flag/advanced-flag.md +++ b/docs/docs/advanced-flag/advanced-flag.md @@ -14,6 +14,9 @@ WebSocket endpoint that sends continuous data streams through the WS protocol. - **Tailwind:** Adds Tailwind CSS support to the project. +- **Testcontainers:** +Adds Testcontainers for Go to set up integration tests for the project. + To utilize the `--advanced` flag, use the following command: ```bash diff --git a/docs/mkdocs.yml b/docs/mkdocs.yml index b4e5581f..ade3b0d0 100644 --- a/docs/mkdocs.yml +++ b/docs/mkdocs.yml @@ -43,6 +43,7 @@ nav: - AF Usage: advanced-flag/advanced-flag.md - HTMX and Templ: advanced-flag/htmx-templ.md - Tailwind CSS: advanced-flag/tailwind.md + - Testcontainers: advanced-flag/testcontainers.md - GoReleaser & GoTest CI: advanced-flag/goreleaser.md - Websocket: advanced-flag/websocket.md - Testing endpoints: From 3c8757df2369dbbd315a5706b5a813244679e2df Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Manuel=20de=20la=20Pe=C3=B1a?= Date: Wed, 3 Jul 2024 14:36:11 +0200 Subject: [PATCH 03/21] chore: add testcontainers to the advanced steps --- cmd/steps/steps.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/cmd/steps/steps.go b/cmd/steps/steps.go index c212ddad..0ec8f256 100644 --- a/cmd/steps/steps.go +++ b/cmd/steps/steps.go @@ -113,6 +113,11 @@ func InitSteps(projectType flags.Framework, databaseType flags.Database) *Steps Title: "TailwindCSS", Desc: "A utility-first CSS framework (selecting this will automatically add HTMX)", }, + { + Flag: "Testcontainers", + Title: "Integration tests with Testcontainers", + Desc: "Unit tests with real dependencies", + }, }, }, }, From 72a97819accb523e962d8225eab6b139dddebc6d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Manuel=20de=20la=20Pe=C3=B1a?= Date: Wed, 3 Jul 2024 16:23:55 +0200 Subject: [PATCH 04/21] chore: bump go version to v1.22.4 --- go.mod | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/go.mod b/go.mod index 77e6d9e4..88501fc4 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/melkeydev/go-blueprint -go 1.20 +go 1.22.4 require ( github.com/charmbracelet/bubbles v0.16.1 From 09e227a1bf9d2ef02d3f78b960175756d85ce822 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Manuel=20de=20la=20Pe=C3=B1a?= Date: Wed, 3 Jul 2024 17:58:29 +0200 Subject: [PATCH 05/21] feat: generate testscontainers tests for the database layer --- cmd/program/program.go | 66 +++++++++++-- cmd/template/testcontainers/files/mongo.tmpl | 65 ++++++++++++ cmd/template/testcontainers/files/mysql.tmpl | 96 ++++++++++++++++++ .../testcontainers/files/postgres.tmpl | 99 +++++++++++++++++++ cmd/template/testcontainers/files/redis.tmpl | 72 ++++++++++++++ cmd/template/testcontainers/mongo.go | 14 +++ cmd/template/testcontainers/mysql.go | 14 +++ cmd/template/testcontainers/postgres.go | 14 +++ cmd/template/testcontainers/redis.go | 14 +++ 9 files changed, 447 insertions(+), 7 deletions(-) create mode 100644 cmd/template/testcontainers/files/mongo.tmpl create mode 100644 cmd/template/testcontainers/files/mysql.tmpl create mode 100644 cmd/template/testcontainers/files/postgres.tmpl create mode 100644 cmd/template/testcontainers/files/redis.tmpl create mode 100644 cmd/template/testcontainers/mongo.go create mode 100644 cmd/template/testcontainers/mysql.go create mode 100644 cmd/template/testcontainers/postgres.go create mode 100644 cmd/template/testcontainers/redis.go diff --git a/cmd/program/program.go b/cmd/program/program.go index 8d5ec77a..2523aca5 100644 --- a/cmd/program/program.go +++ b/cmd/program/program.go @@ -18,6 +18,7 @@ import ( "github.com/melkeydev/go-blueprint/cmd/template/dbdriver" "github.com/melkeydev/go-blueprint/cmd/template/docker" "github.com/melkeydev/go-blueprint/cmd/template/framework" + "github.com/melkeydev/go-blueprint/cmd/template/testcontainers" "github.com/melkeydev/go-blueprint/cmd/utils" "github.com/spf13/cobra" ) @@ -34,6 +35,7 @@ type Project struct { FrameworkMap map[flags.Framework]Framework DBDriverMap map[flags.Database]Driver DockerMap map[flags.Database]Docker + TestcontainersMap map[flags.Database]Testcontainers AdvancedOptions map[string]bool AdvancedTemplates AdvancedTemplates } @@ -60,6 +62,11 @@ type Docker struct { templater DockerTemplater } +type Testcontainers struct { + packageName []string + templater TestcontainersTemplater +} + // A Templater has the methods that help build the files // in the Project folder, and is specific to a Framework type Templater interface { @@ -81,6 +88,10 @@ type DockerTemplater interface { Docker() []byte } +type TestcontainersTemplater interface { + Testcontainers() []byte +} + type WorkflowTemplater interface { Releaser() []byte Test() []byte @@ -106,13 +117,14 @@ var ( ) const ( - root = "/" - cmdApiPath = "cmd/api" - cmdWebPath = "cmd/web" - internalServerPath = "internal/server" - internalDatabasePath = "internal/database" - gitHubActionPath = ".github/workflows" - testHandlerPath = "tests" + root = "/" + cmdApiPath = "cmd/api" + cmdWebPath = "cmd/web" + internalServerPath = "internal/server" + internalDatabasePath = "internal/database" + gitHubActionPath = ".github/workflows" + testHandlerPath = "tests" + testcontainersPackage string = "github.com/testcontainers/testcontainers-go" ) // ExitCLI checks if the Project has been exited, and closes @@ -210,6 +222,27 @@ func (p *Project) createDockerMap() { } } +func (p *Project) createTestcontainersMap() { + p.TestcontainersMap = make(map[flags.Database]Testcontainers) + + p.TestcontainersMap[flags.MySql] = Testcontainers{ + packageName: []string{testcontainersPackage + "/modules/mysql"}, + templater: testcontainers.MysqlTestcontainersTemplate{}, + } + p.TestcontainersMap[flags.Postgres] = Testcontainers{ + packageName: []string{testcontainersPackage + "/modules/postgres"}, + templater: testcontainers.PostgresTestcontainersTemplate{}, + } + p.TestcontainersMap[flags.Mongo] = Testcontainers{ + packageName: []string{testcontainersPackage + "/modules/mongodb"}, + templater: testcontainers.MongoTestcontainersTemplate{}, + } + p.TestcontainersMap[flags.Redis] = Testcontainers{ + packageName: []string{testcontainersPackage + "/modules/redis"}, + templater: testcontainers.RedisTestcontainersTemplate{}, + } +} + // CreateMainFile creates the project folders and files, // and writes to them depending on the selected options func (p *Project) CreateMainFile() error { @@ -315,6 +348,22 @@ func (p *Project) CreateMainFile() error { } } + // Create Testcontainers tests for the selected database driver + if p.DBDriver != "none" { + if p.DBDriver != "sqlite" { + p.createTestcontainersMap() + + err = p.CreateFileWithInjection(internalDatabasePath, projectPath, "database_test.go", "testcontainers") + if err != nil { + log.Printf("Error injecting database_test.go file: %v", err) + cobra.CheckErr(err) + return err + } + } else { + fmt.Println(" We are unable to create database_test.go file for an SQLite database") + } + } + // Install the godotenv package err = utils.GoGetPackage(projectPath, godotenvPackage) if err != nil { @@ -709,6 +758,9 @@ func (p *Project) CreateFileWithInjection(pathToCreate string, projectPath strin case "db-docker": createdTemplate := template.Must(template.New(fileName).Parse(string(p.DockerMap[p.Docker].templater.Docker()))) err = createdTemplate.Execute(createdFile, p) + case "testcontainers": + createdTemplate := template.Must(template.New(fileName).Parse(string(p.TestcontainersMap[p.DBDriver].templater.Testcontainers()))) + err = createdTemplate.Execute(createdFile, p) case "tests": createdTemplate := template.Must(template.New(fileName).Parse(string(p.FrameworkMap[p.ProjectType].templater.TestHandler()))) err = createdTemplate.Execute(createdFile, p) diff --git a/cmd/template/testcontainers/files/mongo.tmpl b/cmd/template/testcontainers/files/mongo.tmpl new file mode 100644 index 00000000..42325e38 --- /dev/null +++ b/cmd/template/testcontainers/files/mongo.tmpl @@ -0,0 +1,65 @@ +package database + +import ( + "context" + "log" + "testing" + + "github.com/testcontainers/testcontainers-go" + "github.com/testcontainers/testcontainers-go/modules/mongodb" +) + +func mustStartMongoContainer() (func(context.Context) error, error) { + dbContainer, err := mongodb.RunContainer( + context.Background(), + testcontainers.WithImage("mongo:latest"), + ) + if err != nil { + return nil, err + } + + dbHost, err := dbContainer.Host(context.Background()) + if err != nil { + return dbContainer.Terminate, err + } + + dbPort, err := dbContainer.MappedPort(context.Background(), "27017/tcp") + if err != nil { + return dbContainer.Terminate, err + } + + host = dbHost + port = dbPort.Port() + + return dbContainer.Terminate, err +} + +func TestMain(m *testing.M) { + teardown, err := mustStartMongoContainer() + if err != nil { + log.Fatalf("could not start postgres container: %v", err) + } + + m.Run() + + if teardown != nil && teardown(context.Background()) != nil { + log.Fatalf("could not teardown postgres container: %v", err) + } +} + +func TestNew(t *testing.T) { + srv := New() + if srv == nil { + t.Fatal("New() returned nil") + } +} + +func TestHealth(t *testing.T) { + srv := New() + + stats := srv.Health() + + if stats["message"] != "It's healthy" { + t.Fatalf("expected message to be 'It's healthy', got %s", stats["message"]) + } +} diff --git a/cmd/template/testcontainers/files/mysql.tmpl b/cmd/template/testcontainers/files/mysql.tmpl new file mode 100644 index 00000000..427cf1d6 --- /dev/null +++ b/cmd/template/testcontainers/files/mysql.tmpl @@ -0,0 +1,96 @@ +package database + +import ( + "context" + "log" + "testing" + "time" + + "github.com/testcontainers/testcontainers-go" + "github.com/testcontainers/testcontainers-go/modules/mysql" + "github.com/testcontainers/testcontainers-go/wait" +) + +func mustStartMySQLContainer() (func(context.Context) error, error) { + var ( + dbName = "database" + dbPwd = "password" + dbUser = "user" + ) + + dbContainer, err := mysql.RunContainer(context.Background(), + testcontainers.WithImage("mysql:8.0.36"), + mysql.WithDatabase(dbName), + mysql.WithUsername(dbUser), + mysql.WithPassword(dbPwd), + testcontainers.WithWaitStrategy(wait.ForLog("port: 3306 MySQL Community Server - GPL").WithStartupTimeout(30*time.Second)), + ) + if err != nil { + return nil, err + } + + dbname = dbName + password = dbPwd + username = dbUser + + dbHost, err := dbContainer.Host(context.Background()) + if err != nil { + return dbContainer.Terminate, err + } + + dbPort, err := dbContainer.MappedPort(context.Background(), "3306/tcp") + if err != nil { + return dbContainer.Terminate, err + } + + host = dbHost + port = dbPort.Port() + + return dbContainer.Terminate, err +} + +func TestMain(m *testing.M) { + teardown, err := mustStartMySQLContainer() + if err != nil { + log.Fatalf("could not start mysql container: %v", err) + } + + m.Run() + + if teardown != nil && teardown(context.Background()) != nil { + log.Fatalf("could not teardown mysql container: %v", err) + } +} + +func TestNew(t *testing.T) { + srv := New() + if srv == nil { + t.Fatal("New() returned nil") + } +} + +func TestHealth(t *testing.T) { + srv := New() + + stats := srv.Health() + + if stats["status"] != "up" { + t.Fatalf("expected status to be up, got %s", stats["status"]) + } + + if _, ok := stats["error"]; ok { + t.Fatalf("expected error not to be present") + } + + if stats["message"] != "It's healthy" { + t.Fatalf("expected message to be 'It's healthy', got %s", stats["message"]) + } +} + +func TestClose(t *testing.T) { + srv := New() + + if srv.Close() != nil { + t.Fatalf("expected Close() to return nil") + } +} diff --git a/cmd/template/testcontainers/files/postgres.tmpl b/cmd/template/testcontainers/files/postgres.tmpl new file mode 100644 index 00000000..3a5d7854 --- /dev/null +++ b/cmd/template/testcontainers/files/postgres.tmpl @@ -0,0 +1,99 @@ +package database + +import ( + "context" + "log" + "testing" + "time" + + "github.com/testcontainers/testcontainers-go" + "github.com/testcontainers/testcontainers-go/modules/postgres" + "github.com/testcontainers/testcontainers-go/wait" +) + +func mustStartPostgresContainer() (func(context.Context) error, error) { + var ( + dbName = "database" + dbPwd = "password" + dbUser = "user" + ) + + dbContainer, err := postgres.RunContainer( + context.Background(), testcontainers.WithImage("postgres:latest"), + postgres.WithDatabase(dbName), + postgres.WithUsername(dbUser), + postgres.WithPassword(dbPwd), + testcontainers.WithWaitStrategy( + wait.ForLog("database system is ready to accept connections"). + WithOccurrence(2). + WithStartupTimeout(5*time.Second)), + ) + if err != nil { + return nil, err + } + + database = dbName + password = dbPwd + username = dbUser + + dbHost, err := dbContainer.Host(context.Background()) + if err != nil { + return dbContainer.Terminate, err + } + + dbPort, err := dbContainer.MappedPort(context.Background(), "5432/tcp") + if err != nil { + return dbContainer.Terminate, err + } + + host = dbHost + port = dbPort.Port() + + return dbContainer.Terminate, err +} + +func TestMain(m *testing.M) { + teardown, err := mustStartPostgresContainer() + if err != nil { + log.Fatalf("could not start postgres container: %v", err) + } + + m.Run() + + if teardown != nil && teardown(context.Background()) != nil { + log.Fatalf("could not teardown postgres container: %v", err) + } +} + +func TestNew(t *testing.T) { + srv := New() + if srv == nil { + t.Fatal("New() returned nil") + } +} + +func TestHealth(t *testing.T) { + srv := New() + + stats := srv.Health() + + if stats["status"] != "up" { + t.Fatalf("expected status to be up, got %s", stats["status"]) + } + + if _, ok := stats["error"]; ok { + t.Fatalf("expected error not to be present") + } + + if stats["message"] != "It's healthy" { + t.Fatalf("expected message to be 'It's healthy', got %s", stats["message"]) + } +} + +func TestClose(t *testing.T) { + srv := New() + + if srv.Close() != nil { + t.Fatalf("expected Close() to return nil") + } +} diff --git a/cmd/template/testcontainers/files/redis.tmpl b/cmd/template/testcontainers/files/redis.tmpl new file mode 100644 index 00000000..80acb77d --- /dev/null +++ b/cmd/template/testcontainers/files/redis.tmpl @@ -0,0 +1,72 @@ +package database + +import ( + "context" + "log" + "testing" + + "github.com/testcontainers/testcontainers-go" + "github.com/testcontainers/testcontainers-go/modules/redis" +) + +func mustStartRedisContainer() (func(context.Context) error, error) { + dbContainer, err := redis.RunContainer( + context.Background(), + testcontainers.WithImage("docker.io/redis:7.2.4"), + redis.WithSnapshotting(10, 1), + redis.WithLogLevel(redis.LogLevelVerbose), + ) + if err != nil { + return nil, err + } + + dbHost, err := dbContainer.Host(context.Background()) + if err != nil { + return dbContainer.Terminate, err + } + + dbPort, err := dbContainer.MappedPort(context.Background(), "6379/tcp") + if err != nil { + return dbContainer.Terminate, err + } + + address = dbHost + port = dbPort.Port() + database = "0" + + return dbContainer.Terminate, err +} + +func TestMain(m *testing.M) { + teardown, err := mustStartRedisContainer() + if err != nil { + log.Fatalf("could not start redis container: %v", err) + } + + m.Run() + + if teardown != nil && teardown(context.Background()) != nil { + log.Fatalf("could not teardown redis container: %v", err) + } +} + +func TestNew(t *testing.T) { + srv := New() + if srv == nil { + t.Fatal("New() returned nil") + } +} + +func TestHealth(t *testing.T) { + srv := New() + + stats := srv.Health() + + if stats["redis_status"] != "up" { + t.Fatalf("expected status to be up, got %s", stats["redis_status"]) + } + + if _, ok := stats["redis_version"]; !ok { + t.Fatalf("expected redis_version to be present, got %v", stats["redis_version"]) + } +} diff --git a/cmd/template/testcontainers/mongo.go b/cmd/template/testcontainers/mongo.go new file mode 100644 index 00000000..67b67729 --- /dev/null +++ b/cmd/template/testcontainers/mongo.go @@ -0,0 +1,14 @@ +package testcontainers + +import ( + _ "embed" +) + +type MongoTestcontainersTemplate struct{} + +//go:embed files/mongo.tmpl +var mongoTestcontainersTemplate []byte + +func (m MongoTestcontainersTemplate) Testcontainers() []byte { + return mongoTestcontainersTemplate +} diff --git a/cmd/template/testcontainers/mysql.go b/cmd/template/testcontainers/mysql.go new file mode 100644 index 00000000..51d6f5bb --- /dev/null +++ b/cmd/template/testcontainers/mysql.go @@ -0,0 +1,14 @@ +package testcontainers + +import ( + _ "embed" +) + +type MysqlTestcontainersTemplate struct{} + +//go:embed files/mysql.tmpl +var mysqlTestcontainersTemplate []byte + +func (m MysqlTestcontainersTemplate) Testcontainers() []byte { + return mysqlTestcontainersTemplate +} diff --git a/cmd/template/testcontainers/postgres.go b/cmd/template/testcontainers/postgres.go new file mode 100644 index 00000000..d5c82cf6 --- /dev/null +++ b/cmd/template/testcontainers/postgres.go @@ -0,0 +1,14 @@ +package testcontainers + +import ( + _ "embed" +) + +type PostgresTestcontainersTemplate struct{} + +//go:embed files/postgres.tmpl +var postgresTestcontainersTemplate []byte + +func (m PostgresTestcontainersTemplate) Testcontainers() []byte { + return postgresTestcontainersTemplate +} diff --git a/cmd/template/testcontainers/redis.go b/cmd/template/testcontainers/redis.go new file mode 100644 index 00000000..ea4c2426 --- /dev/null +++ b/cmd/template/testcontainers/redis.go @@ -0,0 +1,14 @@ +package testcontainers + +import ( + _ "embed" +) + +type RedisTestcontainersTemplate struct{} + +//go:embed files/redis.tmpl +var redisTestcontainersTemplate []byte + +func (r RedisTestcontainersTemplate) Testcontainers() []byte { + return redisTestcontainersTemplate +} From 3d8da48667953690f1c4534cdd767882f211d76e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Manuel=20de=20la=20Pe=C3=B1a?= Date: Wed, 3 Jul 2024 18:05:04 +0200 Subject: [PATCH 06/21] docs: document testcontainers tests --- docs/docs/advanced-flag/testcontainers.md | 66 ++++++++++++++++++++++ docs/docs/creating-project/project-init.md | 7 ++- docs/docs/index.md | 1 + 3 files changed, 73 insertions(+), 1 deletion(-) create mode 100644 docs/docs/advanced-flag/testcontainers.md diff --git a/docs/docs/advanced-flag/testcontainers.md b/docs/docs/advanced-flag/testcontainers.md new file mode 100644 index 00000000..06476f0e --- /dev/null +++ b/docs/docs/advanced-flag/testcontainers.md @@ -0,0 +1,66 @@ +[Testcontainers for Go](https://golang.testcontainers.org/) is a Go package that makes it simple to create and clean up container-based dependencies for automated integration/smoke tests. The clean, easy-to-use API enables developers to programmatically define containers that should be run as part of a test and clean up those resources when the test is done. + + +The project tree would look like this: +```bash +/ (Root) +├── .github/ +│ └── workflows/ +│ ├── go-test.yml # GitHub Actions workflow for running tests. +│ └── release.yml # GitHub Actions workflow for releasing the application. +├── cmd/ +│ ├── api/ +│ │ └── main.go # Main file for starting the server. +│ └── web/ +│ ├── assets/ +│ │ ├── css/ +│ │ │ ├── input.css # Tailwind input file for compiling output.css with CLI +│ │ │ └── output.css # Generated CSS file. +│ │ └── js/ +│ │ └── htmx.min.js # HTMX library for dynamic HTML content. +│ ├── base.templ # Base HTML template file. +│ ├── base_templ.go # Generated Go code for base template +│ ├── efs.go # Includes assets into compiled binary. +│ ├── hello.go # Logic for handling "hello" form. +│ ├── hello.templ # Template file for the "hello" endpoint. +│ └── hello_templ.go # Generated Go code for the "hello" template. +├── internal/ +│ ├── database/ +│ │ └── database_test.go # File containing integrations tests for the database operations. +│ │ └── database.go # File containing functions related to database operations. +│ └── server/ +│ ├── routes.go # File defining HTTP routes. +│ └── server.go # Main server logic. +├── tests/ +│ └── handler_test.go # Test file for testing HTTP handlers. +├── .air.toml # Configuration file for Air, a live-reload utility. +├── docker-compose.yml # Docker Compose configuration for defining DB config. +├── .env # Environment configuration file. +├── .gitignore # File specifying which files and directories to ignore in Git. +├── go.mod # Go module file for managing dependencies. +├── .goreleaser.yml # Configuration file for GoReleaser, a tool for building and releasing binaries. +├── go.sum # Go module file containing checksums for dependencies. +├── Makefile # Makefile for defining and running commands. +├── tailwind.config.js # Tailwind CSS configuration file. +└── README.md # Project's README file containing essential information about the project. +``` + +## Requirements + +You need a container runtime installed on your machine. Testcontainers supports Docker and any other container runtime that implements the Docker APIs. + +To install Docker: + +```bash +curl -sLO get.docker.com +``` + +## Running the tests + +Go to the `internal/database` directory and run the following command: + +```bash +go test -v +``` + +Testcontainers automatically downloads the required Docker images and start the containers. The tests run against the containers, and once the tests are done, the containers are stopped and removed. For further information, refer to the [official documentation](https://golang.testcontainers.org/). diff --git a/docs/docs/creating-project/project-init.md b/docs/docs/creating-project/project-init.md index b25f322e..589b25b2 100644 --- a/docs/docs/creating-project/project-init.md +++ b/docs/docs/creating-project/project-init.md @@ -65,7 +65,12 @@ TailwindCSS: go-blueprint create --advanced --feature tailwind ``` +Testcontainers: +```bash +go-blueprint create --advanced --feature testcontainers +``` + Or all features at once: ```bash -go-blueprint create --name my-project --framework chi --driver mysql --advanced --feature htmx --feature githubaction --feature websocket --feature tailwind +go-blueprint create --name my-project --framework chi --driver mysql --advanced --feature htmx --feature githubaction --feature websocket --feature tailwind --feature testcontainers ``` diff --git a/docs/docs/index.md b/docs/docs/index.md index a795e89a..984484ef 100644 --- a/docs/docs/index.md +++ b/docs/docs/index.md @@ -47,6 +47,7 @@ Here's an overview of the project structure created by Go Blueprint when all opt │ └── hello_templ.go # Generated Go code for the "hello" template. ├── internal/ │ ├── database/ +│ │ └── database_test.go # File containing integrations tests for the database operations. │ │ └── database.go # File containing functions related to database operations. │ └── server/ │ ├── routes.go # File defining HTTP routes. From c091327708e1815da759b4a14d32c322e9f4fc39 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Manuel=20de=20la=20Pe=C3=B1a?= Date: Thu, 4 Jul 2024 09:36:59 +0200 Subject: [PATCH 07/21] chore: add CI workflow running the generated tests for each DB driver --- .github/workflows/testcontainers.yml | 40 ++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) create mode 100644 .github/workflows/testcontainers.yml diff --git a/.github/workflows/testcontainers.yml b/.github/workflows/testcontainers.yml new file mode 100644 index 00000000..fb61a4b6 --- /dev/null +++ b/.github/workflows/testcontainers.yml @@ -0,0 +1,40 @@ +name: Test Generated Testcontainers Blueprints + +on: + pull_request: {} + workflow_dispatch: {} + +jobs: + testcontainers_matrix: + strategy: + matrix: + driver: + [mysql, postgres, sqlite, mongo, redis, none] + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Setup Go + uses: actions/setup-go@v5 + with: + go-version: '1.22.2' + + - name: Commit report + run: | + git config --global user.name 'testname' + git config --global user.email 'testemail@users.noreply.github.com' + + - name: build ${{ matrix.framework }} template + run: script -q /dev/null -c "go run main.go create -n ${{ matrix.framework }} -f ${{ matrix.framework}} -d ${{matrix.driver}} --advanced true --feature testcontainers" /dev/null + + - name: install gotestsum + working-directory: ${{ matrix.framework }} + run: | + go install gotest.tools/gotestsum@latest + + - name: run ${{ matrix.framework }} integration tests + working-directory: ${{ matrix.framework }} + run: | + gotestsum --format short-verbose --packages="./..." --junitfile TEST-${{ matrix.framework }}.xml -- -coverprofile=coverage.out + + - name: remove ${{ matrix.framework }} template + run: rm -rf ${{ matrix.framework }} From b43cf4b4dc110138c0cbfb9c043647e19c36ab96 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Manuel=20de=20la=20Pe=C3=B1a?= Date: Thu, 4 Jul 2024 09:41:39 +0200 Subject: [PATCH 08/21] fix: pass a framework in order to actually build the app --- .github/workflows/testcontainers.yml | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/.github/workflows/testcontainers.yml b/.github/workflows/testcontainers.yml index fb61a4b6..5069eaf6 100644 --- a/.github/workflows/testcontainers.yml +++ b/.github/workflows/testcontainers.yml @@ -23,18 +23,18 @@ jobs: git config --global user.name 'testname' git config --global user.email 'testemail@users.noreply.github.com' - - name: build ${{ matrix.framework }} template - run: script -q /dev/null -c "go run main.go create -n ${{ matrix.framework }} -f ${{ matrix.framework}} -d ${{matrix.driver}} --advanced true --feature testcontainers" /dev/null + - name: build ${{ matrix.driver }} template + run: script -q /dev/null -c "go run main.go create -n ${{ matrix.driver }} -f fiber -d ${{matrix.driver}} --advanced true --feature testcontainers" /dev/null - name: install gotestsum - working-directory: ${{ matrix.framework }} + working-directory: ${{ matrix.driver }} run: | go install gotest.tools/gotestsum@latest - - name: run ${{ matrix.framework }} integration tests - working-directory: ${{ matrix.framework }} + - name: run ${{ matrix.driver }} integration tests + working-directory: ${{ matrix.driver }} run: | - gotestsum --format short-verbose --packages="./..." --junitfile TEST-${{ matrix.framework }}.xml -- -coverprofile=coverage.out + gotestsum --format short-verbose --packages="./..." --junitfile TEST-${{ matrix.driver }}.xml -- -coverprofile=coverage.out - - name: remove ${{ matrix.framework }} template - run: rm -rf ${{ matrix.framework }} + - name: remove ${{ matrix.driver }} template + run: rm -rf ${{ matrix.driver }} From a7e3339b2edc43a069b5f4f9e9b24821e01041f5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Manuel=20de=20la=20Pe=C3=B1a?= Date: Thu, 4 Jul 2024 09:45:25 +0200 Subject: [PATCH 09/21] chore: run integration tests, only --- .github/workflows/testcontainers.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/testcontainers.yml b/.github/workflows/testcontainers.yml index 5069eaf6..42057edd 100644 --- a/.github/workflows/testcontainers.yml +++ b/.github/workflows/testcontainers.yml @@ -34,7 +34,7 @@ jobs: - name: run ${{ matrix.driver }} integration tests working-directory: ${{ matrix.driver }} run: | - gotestsum --format short-verbose --packages="./..." --junitfile TEST-${{ matrix.driver }}.xml -- -coverprofile=coverage.out + gotestsum --format short-verbose --packages="./internal/database" --junitfile TEST-${{ matrix.driver }}.xml -- -coverprofile=coverage.out - name: remove ${{ matrix.driver }} template run: rm -rf ${{ matrix.driver }} From 3d6ce43f23a33080f4d57a8062edfaf017494656 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Manuel=20de=20la=20Pe=C3=B1a?= Date: Thu, 4 Jul 2024 09:46:53 +0200 Subject: [PATCH 10/21] fix: remove none driver from the list --- .github/workflows/testcontainers.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/testcontainers.yml b/.github/workflows/testcontainers.yml index 42057edd..10c77e0f 100644 --- a/.github/workflows/testcontainers.yml +++ b/.github/workflows/testcontainers.yml @@ -9,7 +9,7 @@ jobs: strategy: matrix: driver: - [mysql, postgres, sqlite, mongo, redis, none] + [mysql, postgres, sqlite, mongo, redis] runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 From 08239b2c640dc8137c1ed12b2e7871eb46b0997e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Manuel=20de=20la=20Pe=C3=B1a?= Date: Thu, 4 Jul 2024 16:22:07 +0200 Subject: [PATCH 11/21] chore: run tests from makefile --- .github/workflows/testcontainers.yml | 8 +------- cmd/template/framework/files/makefile.tmpl | 5 +++++ 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/.github/workflows/testcontainers.yml b/.github/workflows/testcontainers.yml index 10c77e0f..6876fb3b 100644 --- a/.github/workflows/testcontainers.yml +++ b/.github/workflows/testcontainers.yml @@ -26,15 +26,9 @@ jobs: - name: build ${{ matrix.driver }} template run: script -q /dev/null -c "go run main.go create -n ${{ matrix.driver }} -f fiber -d ${{matrix.driver}} --advanced true --feature testcontainers" /dev/null - - name: install gotestsum - working-directory: ${{ matrix.driver }} - run: | - go install gotest.tools/gotestsum@latest - - name: run ${{ matrix.driver }} integration tests working-directory: ${{ matrix.driver }} - run: | - gotestsum --format short-verbose --packages="./internal/database" --junitfile TEST-${{ matrix.driver }}.xml -- -coverprofile=coverage.out + run: make itest - name: remove ${{ matrix.driver }} template run: rm -rf ${{ matrix.driver }} diff --git a/cmd/template/framework/files/makefile.tmpl b/cmd/template/framework/files/makefile.tmpl index f6ec1255..22f44189 100644 --- a/cmd/template/framework/files/makefile.tmpl +++ b/cmd/template/framework/files/makefile.tmpl @@ -36,6 +36,11 @@ test: @echo "Testing..." @go test ./tests -v +# Integrations Tests for the application +itest: + @echo "Running integration tests..." + @go test ./internal/database -v + # Clean the binary clean: @echo "Cleaning..." From 668f10579f54263b2face4bd7677da1982851520 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Manuel=20de=20la=20Pe=C3=B1a?= Date: Thu, 4 Jul 2024 16:33:21 +0200 Subject: [PATCH 12/21] fix: do not generate tests for sqlite --- .github/workflows/testcontainers.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/testcontainers.yml b/.github/workflows/testcontainers.yml index 6876fb3b..83f6e435 100644 --- a/.github/workflows/testcontainers.yml +++ b/.github/workflows/testcontainers.yml @@ -9,7 +9,7 @@ jobs: strategy: matrix: driver: - [mysql, postgres, sqlite, mongo, redis] + [mysql, postgres, mongo, redis] runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 From c4ea4b38eeb120fab3f3a56772a9e4727e1482a7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Manuel=20de=20la=20Pe=C3=B1a?= Date: Tue, 16 Jul 2024 17:57:45 +0200 Subject: [PATCH 13/21] fix: update module APIs --- cmd/template/testcontainers/files/mongo.tmpl | 6 +----- cmd/template/testcontainers/files/mysql.tmpl | 4 ++-- cmd/template/testcontainers/files/postgres.tmpl | 5 +++-- cmd/template/testcontainers/files/redis.tmpl | 5 ++--- 4 files changed, 8 insertions(+), 12 deletions(-) diff --git a/cmd/template/testcontainers/files/mongo.tmpl b/cmd/template/testcontainers/files/mongo.tmpl index 42325e38..956b46cb 100644 --- a/cmd/template/testcontainers/files/mongo.tmpl +++ b/cmd/template/testcontainers/files/mongo.tmpl @@ -5,15 +5,11 @@ import ( "log" "testing" - "github.com/testcontainers/testcontainers-go" "github.com/testcontainers/testcontainers-go/modules/mongodb" ) func mustStartMongoContainer() (func(context.Context) error, error) { - dbContainer, err := mongodb.RunContainer( - context.Background(), - testcontainers.WithImage("mongo:latest"), - ) + dbContainer, err := mongodb.Run(context.Background(), "mongo:latest") if err != nil { return nil, err } diff --git a/cmd/template/testcontainers/files/mysql.tmpl b/cmd/template/testcontainers/files/mysql.tmpl index 427cf1d6..3dce6f3f 100644 --- a/cmd/template/testcontainers/files/mysql.tmpl +++ b/cmd/template/testcontainers/files/mysql.tmpl @@ -18,8 +18,8 @@ func mustStartMySQLContainer() (func(context.Context) error, error) { dbUser = "user" ) - dbContainer, err := mysql.RunContainer(context.Background(), - testcontainers.WithImage("mysql:8.0.36"), + dbContainer, err := mysql.Run(context.Background(), + "mysql:8.0.36", mysql.WithDatabase(dbName), mysql.WithUsername(dbUser), mysql.WithPassword(dbPwd), diff --git a/cmd/template/testcontainers/files/postgres.tmpl b/cmd/template/testcontainers/files/postgres.tmpl index 3a5d7854..fd46cbbd 100644 --- a/cmd/template/testcontainers/files/postgres.tmpl +++ b/cmd/template/testcontainers/files/postgres.tmpl @@ -18,8 +18,9 @@ func mustStartPostgresContainer() (func(context.Context) error, error) { dbUser = "user" ) - dbContainer, err := postgres.RunContainer( - context.Background(), testcontainers.WithImage("postgres:latest"), + dbContainer, err := postgres.Run( + context.Background(), + "postgres:latest", postgres.WithDatabase(dbName), postgres.WithUsername(dbUser), postgres.WithPassword(dbPwd), diff --git a/cmd/template/testcontainers/files/redis.tmpl b/cmd/template/testcontainers/files/redis.tmpl index 80acb77d..364d8d36 100644 --- a/cmd/template/testcontainers/files/redis.tmpl +++ b/cmd/template/testcontainers/files/redis.tmpl @@ -5,14 +5,13 @@ import ( "log" "testing" - "github.com/testcontainers/testcontainers-go" "github.com/testcontainers/testcontainers-go/modules/redis" ) func mustStartRedisContainer() (func(context.Context) error, error) { - dbContainer, err := redis.RunContainer( + dbContainer, err := redis.Run( context.Background(), - testcontainers.WithImage("docker.io/redis:7.2.4"), + "docker.io/redis:7.2.4", redis.WithSnapshotting(10, 1), redis.WithLogLevel(redis.LogLevelVerbose), ) From abc8447fc4ec17d482605001670f568c6a596b80 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Manuel=20de=20la=20Pe=C3=B1a?= Date: Tue, 16 Jul 2024 18:21:54 +0200 Subject: [PATCH 14/21] chore: move integration tests to the core --- .github/workflows/testcontainers.yml | 2 +- README.md | 1 - cmd/flags/advancedFeatures.go | 3 +- cmd/program/program.go | 60 ++++--------------- cmd/steps/steps.go | 5 -- .../files => dbdriver/files/tests}/mongo.tmpl | 0 .../files => dbdriver/files/tests}/mysql.tmpl | 0 .../files/tests}/postgres.tmpl | 0 .../files => dbdriver/files/tests}/redis.tmpl | 0 cmd/template/dbdriver/mongo.go | 5 ++ cmd/template/dbdriver/mysql.go | 6 ++ cmd/template/dbdriver/postgres.go | 7 +++ cmd/template/dbdriver/redis.go | 7 +++ cmd/template/dbdriver/sqlite.go | 4 ++ cmd/template/testcontainers/mongo.go | 14 ----- cmd/template/testcontainers/mysql.go | 14 ----- cmd/template/testcontainers/postgres.go | 14 ----- cmd/template/testcontainers/redis.go | 14 ----- docs/docs/advanced-flag/advanced-flag.md | 3 - docs/docs/blueprint-core/db-drivers.md | 34 +++++++++++ docs/docs/creating-project/project-init.md | 7 +-- 21 files changed, 77 insertions(+), 123 deletions(-) rename cmd/template/{testcontainers/files => dbdriver/files/tests}/mongo.tmpl (100%) rename cmd/template/{testcontainers/files => dbdriver/files/tests}/mysql.tmpl (100%) rename cmd/template/{testcontainers/files => dbdriver/files/tests}/postgres.tmpl (100%) rename cmd/template/{testcontainers/files => dbdriver/files/tests}/redis.tmpl (100%) delete mode 100644 cmd/template/testcontainers/mongo.go delete mode 100644 cmd/template/testcontainers/mysql.go delete mode 100644 cmd/template/testcontainers/postgres.go delete mode 100644 cmd/template/testcontainers/redis.go diff --git a/.github/workflows/testcontainers.yml b/.github/workflows/testcontainers.yml index 83f6e435..e2c806ff 100644 --- a/.github/workflows/testcontainers.yml +++ b/.github/workflows/testcontainers.yml @@ -24,7 +24,7 @@ jobs: git config --global user.email 'testemail@users.noreply.github.com' - name: build ${{ matrix.driver }} template - run: script -q /dev/null -c "go run main.go create -n ${{ matrix.driver }} -f fiber -d ${{matrix.driver}} --advanced true --feature testcontainers" /dev/null + run: script -q /dev/null -c "go run main.go create -n ${{ matrix.driver }} -f fiber -d ${{matrix.driver}}" /dev/null - name: run ${{ matrix.driver }} integration tests working-directory: ${{ matrix.driver }} diff --git a/README.md b/README.md index cfc84d39..b274d7b1 100644 --- a/README.md +++ b/README.md @@ -112,7 +112,6 @@ You can now use the `--advanced` flag when running the `create` command to get a - CI/CD workflow setup using [Github Actions](https://docs.github.com/en/actions) - [Websocket](https://pkg.go.dev/nhooyr.io/websocket) sets up a websocket endpoint - [Tailwind](https://tailwindcss.com/) css framework -- [Testcontainers](https://testcontainers.com/) sets up integration tests using Testcontainers for Go Note: selecting tailwind option automatically selects htmx. diff --git a/cmd/flags/advancedFeatures.go b/cmd/flags/advancedFeatures.go index 4140d00e..8e8ad899 100644 --- a/cmd/flags/advancedFeatures.go +++ b/cmd/flags/advancedFeatures.go @@ -12,10 +12,9 @@ const ( GoProjectWorkflow string = "githubaction" Websocket string = "websocket" Tailwind string = "tailwind" - Testcontainers string = "testcontainers" ) -var AllowedAdvancedFeatures = []string{string(Htmx), string(GoProjectWorkflow), string(Websocket), string(Tailwind), string(Testcontainers)} +var AllowedAdvancedFeatures = []string{string(Htmx), string(GoProjectWorkflow), string(Websocket), string(Tailwind)} func (f AdvancedFeatures) String() string { return strings.Join(f, ",") diff --git a/cmd/program/program.go b/cmd/program/program.go index 7fcfc65a..0454e838 100644 --- a/cmd/program/program.go +++ b/cmd/program/program.go @@ -18,7 +18,6 @@ import ( "github.com/melkeydev/go-blueprint/cmd/template/dbdriver" "github.com/melkeydev/go-blueprint/cmd/template/docker" "github.com/melkeydev/go-blueprint/cmd/template/framework" - "github.com/melkeydev/go-blueprint/cmd/template/testcontainers" "github.com/melkeydev/go-blueprint/cmd/utils" "github.com/spf13/cobra" ) @@ -35,7 +34,6 @@ type Project struct { FrameworkMap map[flags.Framework]Framework DBDriverMap map[flags.Database]Driver DockerMap map[flags.Database]Docker - TestcontainersMap map[flags.Database]Testcontainers AdvancedOptions map[string]bool AdvancedTemplates AdvancedTemplates GitOptions flags.Git @@ -63,11 +61,6 @@ type Docker struct { templater DockerTemplater } -type Testcontainers struct { - packageName []string - templater TestcontainersTemplater -} - // A Templater has the methods that help build the files // in the Project folder, and is specific to a Framework type Templater interface { @@ -83,16 +76,13 @@ type Templater interface { type DBDriverTemplater interface { Service() []byte Env() []byte + Tests() []byte } type DockerTemplater interface { Docker() []byte } -type TestcontainersTemplater interface { - Testcontainers() []byte -} - type WorkflowTemplater interface { Releaser() []byte Test() []byte @@ -223,27 +213,6 @@ func (p *Project) createDockerMap() { } } -func (p *Project) createTestcontainersMap() { - p.TestcontainersMap = make(map[flags.Database]Testcontainers) - - p.TestcontainersMap[flags.MySql] = Testcontainers{ - packageName: []string{testcontainersPackage + "/modules/mysql"}, - templater: testcontainers.MysqlTestcontainersTemplate{}, - } - p.TestcontainersMap[flags.Postgres] = Testcontainers{ - packageName: []string{testcontainersPackage + "/modules/postgres"}, - templater: testcontainers.PostgresTestcontainersTemplate{}, - } - p.TestcontainersMap[flags.Mongo] = Testcontainers{ - packageName: []string{testcontainersPackage + "/modules/mongodb"}, - templater: testcontainers.MongoTestcontainersTemplate{}, - } - p.TestcontainersMap[flags.Redis] = Testcontainers{ - packageName: []string{testcontainersPackage + "/modules/redis"}, - templater: testcontainers.RedisTestcontainersTemplate{}, - } -} - // CreateMainFile creates the project folders and files, // and writes to them depending on the selected options func (p *Project) CreateMainFile() error { @@ -320,38 +289,31 @@ func (p *Project) CreateMainFile() error { cobra.CheckErr(err) return err } - } - // Create correct docker compose for the selected driver - if p.DBDriver != "none" { if p.DBDriver != "sqlite" { - p.createDockerMap() - p.Docker = p.DBDriver - - err = p.CreateFileWithInjection(root, projectPath, "docker-compose.yml", "db-docker") + err = p.CreateFileWithInjection(internalDatabasePath, projectPath, "database_test.go", "integration-tests") if err != nil { - log.Printf("Error injecting docker-compose.yml file: %v", err) + log.Printf("Error injecting database_test.go file: %v", err) cobra.CheckErr(err) return err } - } else { - fmt.Println(" We are unable to create docker-compose.yml file for an SQLite database") } } - // Create Testcontainers tests for the selected database driver + // Create correct docker compose for the selected driver if p.DBDriver != "none" { if p.DBDriver != "sqlite" { - p.createTestcontainersMap() + p.createDockerMap() + p.Docker = p.DBDriver - err = p.CreateFileWithInjection(internalDatabasePath, projectPath, "database_test.go", "testcontainers") + err = p.CreateFileWithInjection(root, projectPath, "docker-compose.yml", "db-docker") if err != nil { - log.Printf("Error injecting database_test.go file: %v", err) + log.Printf("Error injecting docker-compose.yml file: %v", err) cobra.CheckErr(err) return err } } else { - fmt.Println(" We are unable to create database_test.go file for an SQLite database") + fmt.Println(" We are unable to create docker-compose.yml file for an SQLite database") } } @@ -757,8 +719,8 @@ func (p *Project) CreateFileWithInjection(pathToCreate string, projectPath strin case "db-docker": createdTemplate := template.Must(template.New(fileName).Parse(string(p.DockerMap[p.Docker].templater.Docker()))) err = createdTemplate.Execute(createdFile, p) - case "testcontainers": - createdTemplate := template.Must(template.New(fileName).Parse(string(p.TestcontainersMap[p.DBDriver].templater.Testcontainers()))) + case "integration-tests": + createdTemplate := template.Must(template.New(fileName).Parse(string(p.DBDriverMap[p.DBDriver].templater.Tests()))) err = createdTemplate.Execute(createdFile, p) case "tests": createdTemplate := template.Must(template.New(fileName).Parse(string(p.FrameworkMap[p.ProjectType].templater.TestHandler()))) diff --git a/cmd/steps/steps.go b/cmd/steps/steps.go index d59d68f9..cf18e0f4 100644 --- a/cmd/steps/steps.go +++ b/cmd/steps/steps.go @@ -113,11 +113,6 @@ func InitSteps(projectType flags.Framework, databaseType flags.Database) *Steps Title: "TailwindCSS", Desc: "A utility-first CSS framework (selecting this will automatically add HTMX)", }, - { - Flag: "Testcontainers", - Title: "Integration tests with Testcontainers", - Desc: "Unit tests with real dependencies", - }, }, }, "git": { diff --git a/cmd/template/testcontainers/files/mongo.tmpl b/cmd/template/dbdriver/files/tests/mongo.tmpl similarity index 100% rename from cmd/template/testcontainers/files/mongo.tmpl rename to cmd/template/dbdriver/files/tests/mongo.tmpl diff --git a/cmd/template/testcontainers/files/mysql.tmpl b/cmd/template/dbdriver/files/tests/mysql.tmpl similarity index 100% rename from cmd/template/testcontainers/files/mysql.tmpl rename to cmd/template/dbdriver/files/tests/mysql.tmpl diff --git a/cmd/template/testcontainers/files/postgres.tmpl b/cmd/template/dbdriver/files/tests/postgres.tmpl similarity index 100% rename from cmd/template/testcontainers/files/postgres.tmpl rename to cmd/template/dbdriver/files/tests/postgres.tmpl diff --git a/cmd/template/testcontainers/files/redis.tmpl b/cmd/template/dbdriver/files/tests/redis.tmpl similarity index 100% rename from cmd/template/testcontainers/files/redis.tmpl rename to cmd/template/dbdriver/files/tests/redis.tmpl diff --git a/cmd/template/dbdriver/mongo.go b/cmd/template/dbdriver/mongo.go index 92694d9c..5786932d 100644 --- a/cmd/template/dbdriver/mongo.go +++ b/cmd/template/dbdriver/mongo.go @@ -12,6 +12,8 @@ var mongoServiceTemplate []byte //go:embed files/env/mongo.tmpl var mongoEnvTemplate []byte +//go:embed files/tests/mongo.tmpl +var mongoTestcontainersTemplate []byte func (m MongoTemplate) Service() []byte { return mongoServiceTemplate @@ -21,3 +23,6 @@ func (m MongoTemplate) Env() []byte { return mongoEnvTemplate } +func (m MongoTemplate) Tests() []byte { + return mongoTestcontainersTemplate +} diff --git a/cmd/template/dbdriver/mysql.go b/cmd/template/dbdriver/mysql.go index 8a268e52..a1099e83 100644 --- a/cmd/template/dbdriver/mysql.go +++ b/cmd/template/dbdriver/mysql.go @@ -12,6 +12,9 @@ var mysqlServiceTemplate []byte //go:embed files/env/mysql.tmpl var mysqlEnvTemplate []byte +//go:embed files/tests/mysql.tmpl +var mysqlTestcontainersTemplate []byte + func (m MysqlTemplate) Service() []byte { return mysqlServiceTemplate } @@ -20,3 +23,6 @@ func (m MysqlTemplate) Env() []byte { return mysqlEnvTemplate } +func (m MysqlTemplate) Tests() []byte { + return mysqlTestcontainersTemplate +} diff --git a/cmd/template/dbdriver/postgres.go b/cmd/template/dbdriver/postgres.go index 875836b0..612c6ed2 100644 --- a/cmd/template/dbdriver/postgres.go +++ b/cmd/template/dbdriver/postgres.go @@ -12,6 +12,9 @@ var postgresServiceTemplate []byte //go:embed files/env/postgres.tmpl var postgresEnvTemplate []byte +//go:embed files/tests/postgres.tmpl +var postgresTestcontainersTemplate []byte + func (m PostgresTemplate) Service() []byte { return postgresServiceTemplate } @@ -19,3 +22,7 @@ func (m PostgresTemplate) Service() []byte { func (m PostgresTemplate) Env() []byte { return postgresEnvTemplate } + +func (m PostgresTemplate) Tests() []byte { + return postgresTestcontainersTemplate +} diff --git a/cmd/template/dbdriver/redis.go b/cmd/template/dbdriver/redis.go index 52e04219..d30156bb 100644 --- a/cmd/template/dbdriver/redis.go +++ b/cmd/template/dbdriver/redis.go @@ -12,6 +12,9 @@ var redisServiceTemplate []byte //go:embed files/env/redis.tmpl var redisEnvTemplate []byte +//go:embed files/tests/redis.tmpl +var redisTestcontainersTemplate []byte + func (r RedisTemplate) Service() []byte { return redisServiceTemplate } @@ -19,3 +22,7 @@ func (r RedisTemplate) Service() []byte { func (r RedisTemplate) Env() []byte { return redisEnvTemplate } + +func (r RedisTemplate) Tests() []byte { + return redisTestcontainersTemplate +} diff --git a/cmd/template/dbdriver/sqlite.go b/cmd/template/dbdriver/sqlite.go index 55a5dccb..9fe4c776 100644 --- a/cmd/template/dbdriver/sqlite.go +++ b/cmd/template/dbdriver/sqlite.go @@ -19,3 +19,7 @@ func (m SqliteTemplate) Service() []byte { func (m SqliteTemplate) Env() []byte { return sqliteEnvTemplate } + +func (m SqliteTemplate) Tests() []byte { + return []byte{} +} diff --git a/cmd/template/testcontainers/mongo.go b/cmd/template/testcontainers/mongo.go deleted file mode 100644 index 67b67729..00000000 --- a/cmd/template/testcontainers/mongo.go +++ /dev/null @@ -1,14 +0,0 @@ -package testcontainers - -import ( - _ "embed" -) - -type MongoTestcontainersTemplate struct{} - -//go:embed files/mongo.tmpl -var mongoTestcontainersTemplate []byte - -func (m MongoTestcontainersTemplate) Testcontainers() []byte { - return mongoTestcontainersTemplate -} diff --git a/cmd/template/testcontainers/mysql.go b/cmd/template/testcontainers/mysql.go deleted file mode 100644 index 51d6f5bb..00000000 --- a/cmd/template/testcontainers/mysql.go +++ /dev/null @@ -1,14 +0,0 @@ -package testcontainers - -import ( - _ "embed" -) - -type MysqlTestcontainersTemplate struct{} - -//go:embed files/mysql.tmpl -var mysqlTestcontainersTemplate []byte - -func (m MysqlTestcontainersTemplate) Testcontainers() []byte { - return mysqlTestcontainersTemplate -} diff --git a/cmd/template/testcontainers/postgres.go b/cmd/template/testcontainers/postgres.go deleted file mode 100644 index d5c82cf6..00000000 --- a/cmd/template/testcontainers/postgres.go +++ /dev/null @@ -1,14 +0,0 @@ -package testcontainers - -import ( - _ "embed" -) - -type PostgresTestcontainersTemplate struct{} - -//go:embed files/postgres.tmpl -var postgresTestcontainersTemplate []byte - -func (m PostgresTestcontainersTemplate) Testcontainers() []byte { - return postgresTestcontainersTemplate -} diff --git a/cmd/template/testcontainers/redis.go b/cmd/template/testcontainers/redis.go deleted file mode 100644 index ea4c2426..00000000 --- a/cmd/template/testcontainers/redis.go +++ /dev/null @@ -1,14 +0,0 @@ -package testcontainers - -import ( - _ "embed" -) - -type RedisTestcontainersTemplate struct{} - -//go:embed files/redis.tmpl -var redisTestcontainersTemplate []byte - -func (r RedisTestcontainersTemplate) Testcontainers() []byte { - return redisTestcontainersTemplate -} diff --git a/docs/docs/advanced-flag/advanced-flag.md b/docs/docs/advanced-flag/advanced-flag.md index c266cea5..00511142 100644 --- a/docs/docs/advanced-flag/advanced-flag.md +++ b/docs/docs/advanced-flag/advanced-flag.md @@ -14,9 +14,6 @@ WebSocket endpoint that sends continuous data streams through the WS protocol. - **Tailwind:** Adds Tailwind CSS support to the project. -- **Testcontainers:** -Adds Testcontainers for Go to set up integration tests for the project. - To utilize the `--advanced` flag, use the following command: ```bash diff --git a/docs/docs/blueprint-core/db-drivers.md b/docs/docs/blueprint-core/db-drivers.md index 67dd10ac..e9524f57 100644 --- a/docs/docs/blueprint-core/db-drivers.md +++ b/docs/docs/blueprint-core/db-drivers.md @@ -17,6 +17,7 @@ Integrating a database adds a new layer to the project structure, primarily in t │ └── main.go ├── /internal │ ├── /database +│ │ ├── database_test.go │ │ └── database.go │ └── /server │ ├── routes.go @@ -33,6 +34,39 @@ Integrating a database adds a new layer to the project structure, primarily in t Users can select the desired database driver based on their project's specific needs. The chosen driver is then imported into the project, and the `database.go` file is adjusted accordingly to establish a connection and manage interactions with the selected database. +## Integration Tests for Database Operations + +For all the database drivers but the `Sqlite`, integration tests are automatically generated to ensure that the database connection is working correctly. It uses [Testcontainers for Go](https://golang.testcontainers.org/) to spin up a containerized instance of the database server, run the tests, and then tear down the container. + +[Testcontainers for Go](https://golang.testcontainers.org/) is a Go package that makes it simple to create and clean up container-based dependencies for automated integration/smoke tests. The clean, easy-to-use API enables developers to programmatically define containers that should be run as part of a test and clean up those resources when the test is done. + + +### Requirements + +You need a container runtime installed on your machine. Testcontainers supports Docker and any other container runtime that implements the Docker APIs. + +To install Docker: + +```bash +curl -sLO get.docker.com +``` + +### Running the tests + +Go to the `internal/database` directory and run the following command: + +```bash +go test -v +``` + +or just run the following command from the root directory: + +```bash +make itest +``` + +Testcontainers automatically pulls the required Docker images and start the containers. The tests run against the containers, and once the tests are done, the containers are stopped and removed. For further information, refer to the [official documentation](https://golang.testcontainers.org/). + ## Docker-Compose for Quick Database Spinup To facilitate quick setup and testing, a `docker-compose.yml` file is provided. This file defines a service for the chosen database system with the necessary environment variables. Running `docker-compose up` will quickly spin up a containerized instance of the database, allowing users to test their application against a real database server. diff --git a/docs/docs/creating-project/project-init.md b/docs/docs/creating-project/project-init.md index b5f18907..ece3e2e0 100644 --- a/docs/docs/creating-project/project-init.md +++ b/docs/docs/creating-project/project-init.md @@ -66,12 +66,7 @@ TailwindCSS: go-blueprint create --advanced --feature tailwind ``` -Testcontainers: -```bash -go-blueprint create --advanced --feature testcontainers -``` - Or all features at once: ```bash -go-blueprint create --name my-project --framework chi --driver mysql --git commit --advanced --feature htmx --feature githubaction --feature websocket --feature tailwind --feature testcontainers +go-blueprint create --name my-project --framework chi --driver mysql --git commit --advanced --feature htmx --feature githubaction --feature websocket --feature tailwind ``` From 7f447a7018b321f452aa9b577e34e11702dbcefc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Manuel=20de=20la=20Pe=C3=B1a?= Date: Tue, 16 Jul 2024 18:25:45 +0200 Subject: [PATCH 15/21] chore: do not add "make itest" goal to sqlite --- cmd/template/framework/files/makefile.tmpl | 2 ++ 1 file changed, 2 insertions(+) diff --git a/cmd/template/framework/files/makefile.tmpl b/cmd/template/framework/files/makefile.tmpl index d9c851ef..1e137c8b 100644 --- a/cmd/template/framework/files/makefile.tmpl +++ b/cmd/template/framework/files/makefile.tmpl @@ -38,10 +38,12 @@ test: @echo "Testing..." @go test ./tests -v +{{if and (ne .DBDriver "none") (ne .DBDriver "sqlite")}} # Integrations Tests for the application itest: @echo "Running integration tests..." @go test ./internal/database -v +{{end}} # Clean the binary clean: From 83ac79fe42ae61d2688d4e919dc59ec5b5e3689c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Manuel=20de=20la=20Pe=C3=B1a?= Date: Tue, 16 Jul 2024 18:27:22 +0200 Subject: [PATCH 16/21] fix: remove entry from docs --- docs/mkdocs.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/docs/mkdocs.yml b/docs/mkdocs.yml index ade3b0d0..b4e5581f 100644 --- a/docs/mkdocs.yml +++ b/docs/mkdocs.yml @@ -43,7 +43,6 @@ nav: - AF Usage: advanced-flag/advanced-flag.md - HTMX and Templ: advanced-flag/htmx-templ.md - Tailwind CSS: advanced-flag/tailwind.md - - Testcontainers: advanced-flag/testcontainers.md - GoReleaser & GoTest CI: advanced-flag/goreleaser.md - Websocket: advanced-flag/websocket.md - Testing endpoints: From d764ba07e255650f8300366056ff4eaac15c03a3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Manuel=20de=20la=20Pe=C3=B1a?= Date: Tue, 16 Jul 2024 18:28:20 +0200 Subject: [PATCH 17/21] chore: use a generic name for integration tests --- .github/workflows/testcontainers.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/testcontainers.yml b/.github/workflows/testcontainers.yml index e2c806ff..33da265e 100644 --- a/.github/workflows/testcontainers.yml +++ b/.github/workflows/testcontainers.yml @@ -1,11 +1,11 @@ -name: Test Generated Testcontainers Blueprints +name: Integrations Test for the Generated Blueprints on: pull_request: {} workflow_dispatch: {} jobs: - testcontainers_matrix: + itests_matrix: strategy: matrix: driver: From e23f599047a5fdb338de5be08828a039d2aeecc6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Manuel=20de=20la=20Pe=C3=B1a?= Date: Tue, 16 Jul 2024 18:28:27 +0200 Subject: [PATCH 18/21] chore: remove unused variable --- cmd/program/program.go | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/cmd/program/program.go b/cmd/program/program.go index 0454e838..698cb673 100644 --- a/cmd/program/program.go +++ b/cmd/program/program.go @@ -108,14 +108,13 @@ var ( ) const ( - root = "/" - cmdApiPath = "cmd/api" - cmdWebPath = "cmd/web" - internalServerPath = "internal/server" - internalDatabasePath = "internal/database" - gitHubActionPath = ".github/workflows" - testHandlerPath = "tests" - testcontainersPackage string = "github.com/testcontainers/testcontainers-go" + root = "/" + cmdApiPath = "cmd/api" + cmdWebPath = "cmd/web" + internalServerPath = "internal/server" + internalDatabasePath = "internal/database" + gitHubActionPath = ".github/workflows" + testHandlerPath = "tests" ) // ExitCLI checks if the Project has been exited, and closes From ca8b484c68227305dd1c471771531751bd7cca41 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Manuel=20de=20la=20Pe=C3=B1a?= Date: Tue, 16 Jul 2024 18:29:55 +0200 Subject: [PATCH 19/21] fix: include git flag in CI --- .github/workflows/testcontainers.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/testcontainers.yml b/.github/workflows/testcontainers.yml index 33da265e..cd605986 100644 --- a/.github/workflows/testcontainers.yml +++ b/.github/workflows/testcontainers.yml @@ -24,7 +24,7 @@ jobs: git config --global user.email 'testemail@users.noreply.github.com' - name: build ${{ matrix.driver }} template - run: script -q /dev/null -c "go run main.go create -n ${{ matrix.driver }} -f fiber -d ${{matrix.driver}}" /dev/null + run: script -q /dev/null -c "go run main.go create -n ${{ matrix.driver }} -g commit -f fiber -d ${{matrix.driver}}" /dev/null - name: run ${{ matrix.driver }} integration tests working-directory: ${{ matrix.driver }} From f70c16f24c0d24774b9b9444bb0b052f32b25ee9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Manuel=20de=20la=20Pe=C3=B1a?= Date: Tue, 23 Jul 2024 15:52:37 +0200 Subject: [PATCH 20/21] chore: use x version for Go 1.22 --- .github/workflows/testcontainers.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/testcontainers.yml b/.github/workflows/testcontainers.yml index cd605986..c924b3fa 100644 --- a/.github/workflows/testcontainers.yml +++ b/.github/workflows/testcontainers.yml @@ -16,7 +16,7 @@ jobs: - name: Setup Go uses: actions/setup-go@v5 with: - go-version: '1.22.2' + go-version: '1.22.x' - name: Commit report run: | From bc2387707dbc3df8de1c7a65bed170e36014a6ad Mon Sep 17 00:00:00 2001 From: ujstor <116409846+Ujstor@users.noreply.github.com> Date: Wed, 24 Jul 2024 13:00:45 +0200 Subject: [PATCH 21/21] Delete docs/docs/advanced-flag/testcontainers.md --- docs/docs/advanced-flag/testcontainers.md | 66 ----------------------- 1 file changed, 66 deletions(-) delete mode 100644 docs/docs/advanced-flag/testcontainers.md diff --git a/docs/docs/advanced-flag/testcontainers.md b/docs/docs/advanced-flag/testcontainers.md deleted file mode 100644 index 06476f0e..00000000 --- a/docs/docs/advanced-flag/testcontainers.md +++ /dev/null @@ -1,66 +0,0 @@ -[Testcontainers for Go](https://golang.testcontainers.org/) is a Go package that makes it simple to create and clean up container-based dependencies for automated integration/smoke tests. The clean, easy-to-use API enables developers to programmatically define containers that should be run as part of a test and clean up those resources when the test is done. - - -The project tree would look like this: -```bash -/ (Root) -├── .github/ -│ └── workflows/ -│ ├── go-test.yml # GitHub Actions workflow for running tests. -│ └── release.yml # GitHub Actions workflow for releasing the application. -├── cmd/ -│ ├── api/ -│ │ └── main.go # Main file for starting the server. -│ └── web/ -│ ├── assets/ -│ │ ├── css/ -│ │ │ ├── input.css # Tailwind input file for compiling output.css with CLI -│ │ │ └── output.css # Generated CSS file. -│ │ └── js/ -│ │ └── htmx.min.js # HTMX library for dynamic HTML content. -│ ├── base.templ # Base HTML template file. -│ ├── base_templ.go # Generated Go code for base template -│ ├── efs.go # Includes assets into compiled binary. -│ ├── hello.go # Logic for handling "hello" form. -│ ├── hello.templ # Template file for the "hello" endpoint. -│ └── hello_templ.go # Generated Go code for the "hello" template. -├── internal/ -│ ├── database/ -│ │ └── database_test.go # File containing integrations tests for the database operations. -│ │ └── database.go # File containing functions related to database operations. -│ └── server/ -│ ├── routes.go # File defining HTTP routes. -│ └── server.go # Main server logic. -├── tests/ -│ └── handler_test.go # Test file for testing HTTP handlers. -├── .air.toml # Configuration file for Air, a live-reload utility. -├── docker-compose.yml # Docker Compose configuration for defining DB config. -├── .env # Environment configuration file. -├── .gitignore # File specifying which files and directories to ignore in Git. -├── go.mod # Go module file for managing dependencies. -├── .goreleaser.yml # Configuration file for GoReleaser, a tool for building and releasing binaries. -├── go.sum # Go module file containing checksums for dependencies. -├── Makefile # Makefile for defining and running commands. -├── tailwind.config.js # Tailwind CSS configuration file. -└── README.md # Project's README file containing essential information about the project. -``` - -## Requirements - -You need a container runtime installed on your machine. Testcontainers supports Docker and any other container runtime that implements the Docker APIs. - -To install Docker: - -```bash -curl -sLO get.docker.com -``` - -## Running the tests - -Go to the `internal/database` directory and run the following command: - -```bash -go test -v -``` - -Testcontainers automatically downloads the required Docker images and start the containers. The tests run against the containers, and once the tests are done, the containers are stopped and removed. For further information, refer to the [official documentation](https://golang.testcontainers.org/).