mirror of
https://github.com/zeromicro/go-zero.git
synced 2026-05-07 15:10:01 +08:00
Add complete test scaffolding support with --test flag for API projects (#5176)
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: kevwan <1918356+kevwan@users.noreply.github.com>
This commit is contained in:
@@ -118,6 +118,8 @@ func DoGenProjectWithModule(apiFile, dir, moduleName, style string, withTest boo
|
|||||||
if withTest {
|
if withTest {
|
||||||
logx.Must(genHandlersTest(dir, rootPkg, projectPkg, cfg, api))
|
logx.Must(genHandlersTest(dir, rootPkg, projectPkg, cfg, api))
|
||||||
logx.Must(genLogicTest(dir, rootPkg, projectPkg, cfg, api))
|
logx.Must(genLogicTest(dir, rootPkg, projectPkg, cfg, api))
|
||||||
|
logx.Must(genServiceContextTest(dir, rootPkg, projectPkg, cfg, api))
|
||||||
|
logx.Must(genIntegrationTest(dir, rootPkg, projectPkg, cfg, api))
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := backupAndSweep(apiFile); err != nil {
|
if err := backupAndSweep(apiFile); err != nil {
|
||||||
|
|||||||
42
tools/goctl/api/gogen/genintegrationtest.go
Normal file
42
tools/goctl/api/gogen/genintegrationtest.go
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
package gogen
|
||||||
|
|
||||||
|
import (
|
||||||
|
_ "embed"
|
||||||
|
|
||||||
|
"github.com/zeromicro/go-zero/tools/goctl/api/spec"
|
||||||
|
"github.com/zeromicro/go-zero/tools/goctl/config"
|
||||||
|
"github.com/zeromicro/go-zero/tools/goctl/internal/version"
|
||||||
|
"github.com/zeromicro/go-zero/tools/goctl/util/format"
|
||||||
|
)
|
||||||
|
|
||||||
|
//go:embed integration_test.tpl
|
||||||
|
var integrationTestTemplate string
|
||||||
|
|
||||||
|
func genIntegrationTest(dir, rootPkg, projectPkg string, cfg *config.Config, api *spec.ApiSpec) error {
|
||||||
|
serviceName := api.Service.Name
|
||||||
|
if len(serviceName) == 0 {
|
||||||
|
serviceName = "server"
|
||||||
|
}
|
||||||
|
|
||||||
|
filename, err := format.FileNamingFormat(cfg.NamingFormat, serviceName)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return genFile(fileGenConfig{
|
||||||
|
dir: dir,
|
||||||
|
subdir: "",
|
||||||
|
filename: filename + "_test.go",
|
||||||
|
templateName: "integrationTestTemplate",
|
||||||
|
category: category,
|
||||||
|
templateFile: integrationTestTemplateFile,
|
||||||
|
builtinTemplate: integrationTestTemplate,
|
||||||
|
data: map[string]any{
|
||||||
|
"projectPkg": projectPkg,
|
||||||
|
"serviceName": serviceName,
|
||||||
|
"version": version.BuildVersion,
|
||||||
|
"hasRoutes": len(api.Service.Routes()) > 0,
|
||||||
|
"routes": api.Service.Routes(),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
34
tools/goctl/api/gogen/gensvctest.go
Normal file
34
tools/goctl/api/gogen/gensvctest.go
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
package gogen
|
||||||
|
|
||||||
|
import (
|
||||||
|
_ "embed"
|
||||||
|
|
||||||
|
"github.com/zeromicro/go-zero/tools/goctl/api/spec"
|
||||||
|
"github.com/zeromicro/go-zero/tools/goctl/config"
|
||||||
|
"github.com/zeromicro/go-zero/tools/goctl/internal/version"
|
||||||
|
"github.com/zeromicro/go-zero/tools/goctl/util/format"
|
||||||
|
)
|
||||||
|
|
||||||
|
//go:embed svc_test.tpl
|
||||||
|
var svcTestTemplate string
|
||||||
|
|
||||||
|
func genServiceContextTest(dir, rootPkg, projectPkg string, cfg *config.Config, api *spec.ApiSpec) error {
|
||||||
|
filename, err := format.FileNamingFormat(cfg.NamingFormat, contextFilename)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return genFile(fileGenConfig{
|
||||||
|
dir: dir,
|
||||||
|
subdir: contextDir,
|
||||||
|
filename: filename + "_test.go",
|
||||||
|
templateName: "svcTestTemplate",
|
||||||
|
category: category,
|
||||||
|
templateFile: svcTestTemplateFile,
|
||||||
|
builtinTemplate: svcTestTemplate,
|
||||||
|
data: map[string]any{
|
||||||
|
"projectPkg": projectPkg,
|
||||||
|
"version": version.BuildVersion,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
120
tools/goctl/api/gogen/integration_test.tpl
Normal file
120
tools/goctl/api/gogen/integration_test.tpl
Normal file
@@ -0,0 +1,120 @@
|
|||||||
|
// Code scaffolded by goctl. Safe to edit.
|
||||||
|
// goctl {{.version}}
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"{{.projectPkg}}/internal/config"
|
||||||
|
"{{.projectPkg}}/internal/handler"
|
||||||
|
"{{.projectPkg}}/internal/svc"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
"github.com/zeromicro/go-zero/rest"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestMain(m *testing.M) {
|
||||||
|
// TODO: Add setup/teardown logic here if needed
|
||||||
|
m.Run()
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestServerIntegration(t *testing.T) {
|
||||||
|
// Create test server
|
||||||
|
c := config.Config{
|
||||||
|
RestConf: rest.RestConf{
|
||||||
|
Host: "127.0.0.1",
|
||||||
|
Port: 0, // Use random available port
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
server := rest.MustNewServer(c.RestConf)
|
||||||
|
defer server.Stop()
|
||||||
|
|
||||||
|
ctx := svc.NewServiceContext(c)
|
||||||
|
handler.RegisterHandlers(server, ctx)
|
||||||
|
|
||||||
|
// Start server in background
|
||||||
|
go func() {
|
||||||
|
server.Start()
|
||||||
|
}()
|
||||||
|
|
||||||
|
// Wait for server to start
|
||||||
|
time.Sleep(100 * time.Millisecond)
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
method string
|
||||||
|
path string
|
||||||
|
body string
|
||||||
|
expectedStatus int
|
||||||
|
setup func()
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "health check",
|
||||||
|
method: "GET",
|
||||||
|
path: "/health",
|
||||||
|
expectedStatus: http.StatusNotFound, // Adjust based on actual routes
|
||||||
|
setup: func() {},
|
||||||
|
},
|
||||||
|
{{if .hasRoutes}}{{range .routes}}{
|
||||||
|
name: "{{.Method}} {{.Path}}",
|
||||||
|
method: "{{.Method}}",
|
||||||
|
path: "{{.Path}}",
|
||||||
|
expectedStatus: http.StatusOK, // TODO: Adjust expected status
|
||||||
|
setup: func() {
|
||||||
|
// TODO: Add setup logic for this endpoint
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{{end}}{{end}}{
|
||||||
|
name: "not found route",
|
||||||
|
method: "GET",
|
||||||
|
path: "/nonexistent",
|
||||||
|
expectedStatus: http.StatusNotFound,
|
||||||
|
setup: func() {},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
tt.setup()
|
||||||
|
|
||||||
|
req, err := http.NewRequest(tt.method, tt.path, nil)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
rr := httptest.NewRecorder()
|
||||||
|
server.ServeHTTP(rr, req)
|
||||||
|
|
||||||
|
assert.Equal(t, tt.expectedStatus, rr.Code)
|
||||||
|
|
||||||
|
// TODO: Add response body assertions
|
||||||
|
t.Logf("Response: %s", rr.Body.String())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestServerLifecycle(t *testing.T) {
|
||||||
|
c := config.Config{
|
||||||
|
RestConf: rest.RestConf{
|
||||||
|
Host: "127.0.0.1",
|
||||||
|
Port: 0,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
server := rest.MustNewServer(c.RestConf)
|
||||||
|
|
||||||
|
// Test server can start and stop without errors
|
||||||
|
ctx := svc.NewServiceContext(c)
|
||||||
|
handler.RegisterHandlers(server, ctx)
|
||||||
|
|
||||||
|
// In a real integration test, you might start the server in a goroutine
|
||||||
|
// and test actual HTTP requests, but for scaffolding we keep it simple
|
||||||
|
server.Stop()
|
||||||
|
|
||||||
|
// TODO: Add more lifecycle tests as needed
|
||||||
|
assert.True(t, true, "Server lifecycle test passed")
|
||||||
|
}
|
||||||
17
tools/goctl/api/gogen/jwt.api
Executable file
17
tools/goctl/api/gogen/jwt.api
Executable file
@@ -0,0 +1,17 @@
|
|||||||
|
type Request {
|
||||||
|
Name string `path:"name,options=you|me"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Response {
|
||||||
|
Message string `json:"message"`
|
||||||
|
}
|
||||||
|
|
||||||
|
@server(
|
||||||
|
jwt: Auth
|
||||||
|
jwtTransition: Trans
|
||||||
|
middleware: TokenValidate
|
||||||
|
)
|
||||||
|
service A-api {
|
||||||
|
@handler GreetHandler
|
||||||
|
get /greet/from/:name(Request) returns (Response)
|
||||||
|
}
|
||||||
60
tools/goctl/api/gogen/svc_test.tpl
Normal file
60
tools/goctl/api/gogen/svc_test.tpl
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
// Code scaffolded by goctl. Safe to edit.
|
||||||
|
// goctl {{.version}}
|
||||||
|
|
||||||
|
package svc
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"{{.projectPkg}}/internal/config"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestNewServiceContext(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
config config.Config
|
||||||
|
setup func() config.Config
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "default config",
|
||||||
|
setup: func() config.Config {
|
||||||
|
return config.Config{}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "valid config",
|
||||||
|
setup: func() config.Config {
|
||||||
|
return config.Config{
|
||||||
|
// TODO: Add valid config values here
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
c := tt.setup()
|
||||||
|
svcCtx := NewServiceContext(c)
|
||||||
|
|
||||||
|
// Basic assertions
|
||||||
|
require.NotNil(t, svcCtx)
|
||||||
|
assert.Equal(t, c, svcCtx.Config)
|
||||||
|
|
||||||
|
// TODO: Add additional assertions for middleware and dependencies
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestServiceContext_Initialization(t *testing.T) {
|
||||||
|
c := config.Config{}
|
||||||
|
svcCtx := NewServiceContext(c)
|
||||||
|
|
||||||
|
// Verify service context is properly initialized
|
||||||
|
assert.NotNil(t, svcCtx)
|
||||||
|
assert.Equal(t, c, svcCtx.Config)
|
||||||
|
|
||||||
|
// TODO: Add tests for middleware initialization if any
|
||||||
|
// TODO: Add tests for external dependencies if any
|
||||||
|
}
|
||||||
@@ -22,6 +22,8 @@ const (
|
|||||||
routesTemplateFile = "routes.tpl"
|
routesTemplateFile = "routes.tpl"
|
||||||
routesAdditionTemplateFile = "route-addition.tpl"
|
routesAdditionTemplateFile = "route-addition.tpl"
|
||||||
typesTemplateFile = "types.tpl"
|
typesTemplateFile = "types.tpl"
|
||||||
|
svcTestTemplateFile = "svc_test.tpl"
|
||||||
|
integrationTestTemplateFile = "integration_test.tpl"
|
||||||
)
|
)
|
||||||
|
|
||||||
var templates = map[string]string{
|
var templates = map[string]string{
|
||||||
@@ -39,6 +41,8 @@ var templates = map[string]string{
|
|||||||
routesTemplateFile: routesTemplate,
|
routesTemplateFile: routesTemplate,
|
||||||
routesAdditionTemplateFile: routesAdditionTemplate,
|
routesAdditionTemplateFile: routesAdditionTemplate,
|
||||||
typesTemplateFile: typesTemplate,
|
typesTemplateFile: typesTemplate,
|
||||||
|
svcTestTemplateFile: svcTestTemplate,
|
||||||
|
integrationTestTemplateFile: integrationTestTemplate,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Category returns the category of the api files.
|
// Category returns the category of the api files.
|
||||||
|
|||||||
Reference in New Issue
Block a user