Featured image of post Notes on Writing Tests with go-zero + gorm

Notes on Writing Tests with go-zero + gorm

Striving to improve our code test coverage

Getting Started

Testing Methods & Approaches

  • export_test.go: Used for test data definitions without affecting production code
  • Setup/Teardown patterns:
func setUp(testName string) func() {
    fmt.Printf("\tsetUp fixture for %s\n", testName)
    return func() {
        fmt.Printf("\ttearDown fixture for %s\n", testName)
    }
}

func TestFunc1(t *testing.T) {
  defer setUp(t.Name())()
  fmt.Printf("\tExecute test: %s\n", t.Name())
}

func TestMain(m *testing.M) {
  defer pkgSetUp("package demo_test")()
  m.Run()
}
  • Unit Testing

    • Core business logic in go-zero services (e.g., logic layer)
    • Mock dependencies using:
    • Alternative mocking solutions: monkey
  • Integration Testing

    • Handle service dependencies using:
      • go-mysql-server for database
      • mini-redis for Redis
      • Or real service instances

Example Repository

├─app
│  ├─id
│  └─post
└─pkg (shared utilities)

Unit Testing

ID Service Implementation

// proto definition remains unchanged

Post Service Implementation

// proto definition remains unchanged

Key Components

  • Service Context:
type ServiceContext struct {
    Config config.Config
    Redis  *redis.Client
    IdRpc  id.IdClient
    Query  *do.Query // GORM generated
}
  • Business Logic:
func (l *GetLogic) Get(in *post.PostRequest) (*post.PostResponse, error) {
    // Database query
    p, err := l.svcCtx.Query.Post.WithContext().Where(...).First()
    
    // Redis increment
    val, err := l.svcCtx.Redis.Incr(...).Result()
    
    // Response mapping
    return &post.PostResponse{...}, nil
}

Unit Test Implementation

  • Test Cases:
func TestGetLogic_Get(t *testing.T) {
    // Mock database response
    mockVal.DatabaseMock.ExpectQuery(...).WillReturnRows(...)
    
    // Test normal flow
    resp, err := logic.Get(&post.PostRequest{Id: 1})
    require.NoError(t, err)
    
    // Test Redis failure
    mockVal.RedisMock.ExpectIncr(...).SetErr(...)
    
    // Test database failure 
    mockVal.DatabaseMock.ExpectQuery(...).WillReturnError(...)
}
  • Mock Infrastructure:
// Creates mocked database, Redis, and ID service
func GetValue() value {
    db, _ := makeDatabase()
    redis, _ := redismock.NewClientMock()
    return value{
        IdServer:     &idMock{},
        Database:     db,
        Redis:        redis,
    }
}

Integration Testing

Service Configuration

  • Modified main function:
func main() {
    ctx := initializeDependencies()
    server := configureGRPCServer(ctx)
    server.Start()
}

Integration Test Setup

  • Test Entrypoint:
func TestMain(m *testing.M) {
    // Initialize fake dependencies
    svcCtxGet = func() (*svc.ServiceContext, error) {
        return &svc.ServiceContext{
            Redis:  fakeRedis,
            IdRpc:  fakeIDService,
            Query:  fakeDB,
        }, nil
    }
    
    go main() // Start service
    os.Exit(m.Run())
}
  • Test Case:
func TestGet(t *testing.T) {
    conn := createGRPCClient()
    resp, err := client.Get(context.Background(), &post.PostRequest{...})
    
    require.NoError(t, err)
    require.Equal(t, expectedID, resp.GetId())
}

Fake Services

  • Database & Redis:
func FakerDatabaseServer() string {
    // Setup in-memory MySQL
}

func FakerRedisServer() (*miniredis.Miniredis, string) {
    // Setup embedded Redis
}

Conclusion

References