使用go-sqlmock时,替换参数为字母数字值后查询不匹配
问题:Go-SQLMock测试GORM查询字母数字ID失败
问题描述
用go-sqlmock测试GORM实现的GetByID函数时,纯数字ID的测试用例能正常通过,但传入字母数字格式的ID(比如100a)就会失败。错误信息显示实际生成的SQL没有用参数占位符,直接把100a拼进了语句里,和预期的带占位符SQL不匹配。
模型定义
type User struct { ID string `gorm:"primaryKey; size:11"` FirstName string `gorm:"NOT NULL; size:255"` LastName string `gorm:"NOT NULL; size:255"` Email string `gorm:"NOT NULL; UNIQUE_INDEX"` Password string `gorm:"NOT NULL"` CreatedAt time.Time UpdatedAt time.Time DeletedAt *time.Time `sql:"index"` }
数据库查询函数
func (u *userRepo) GetByID(id string) (*user.User, AppError) { var user user.User if err := u.db.First(&user, id).Error; err != nil { return nil, NewNotFoundError(err) } return &user, nil }
测试配置与错误现象
测试时用sqlmock.New(sqlmock.QueryMatcherOption(sqlmock.QueryMatcherEqual))初始化sqlmock。传入100a作为ID时,实际生成的SQL是:
SELECT * FROM "users" WHERE "users"."deleted_at" IS NULL AND ((100a)) ORDER BY "users"."id" ASC LIMIT 1
这条SQL没使用参数占位符,和测试预期的带占位符SQL不匹配,导致测试失败。
解决方案
问题根源
GORM的First方法处理字符串参数时会做判断:
- 纯数字字符串会被识别为主键值,生成带占位符的SQL(比如
WHERE id = ?) - 包含非数字的字符串会被当成原始SQL片段直接拼进语句,既存在SQL注入风险,又和sqlmock的预期不匹配
解决步骤
1. 显式指定主键查询条件
修改GetByID函数,明确指定查询的主键字段,让GORM始终生成带参数占位符的SQL:
func (u *userRepo) GetByID(id string) (*user.User, AppError) { var user user.User // 显式指定id字段,避免GORM把字符串解析成SQL片段 if err := u.db.First(&user, "id = ?", id).Error; err != nil { return nil, NewNotFoundError(err) } return &user, nil }
2. 更新sqlmock的预期SQL
根据修改后的查询逻辑,更新测试用例里的预期SQL,注意占位符格式要和数据库驱动匹配(PostgreSQL用$1,MySQL用?):
// 示例测试代码(以PostgreSQL为例) mock.ExpectQuery(`SELECT * FROM "users" WHERE "users"."deleted_at" IS NULL AND "users"."id" = $1 ORDER BY "users"."id" ASC LIMIT 1`). WithArgs("100a"). WillReturnRows(sqlmock.NewRows([]string{"id", "first_name", "last_name", "email", "password"}). AddRow("100a", "John", "Doe", "john@example.com", "hashed_pass"))
3. 可选:用结构体或Map指定查询条件
除了显式写SQL条件,也可以用结构体或Map来指定查询条件,效果一致:
// 用结构体查询 if err := u.db.First(&user, user.User{ID: id}).Error; err != nil { return nil, NewNotFoundError(err) } // 用Map查询 if err := u.db.First(&user, map[string]interface{}{"id": id}).Error; err != nil { return nil, NewNotFoundError(err) }
内容的提问来源于stack exchange,提问作者Najeeb Ullah Shah
相关产品推荐
相关产品推荐

