You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.18 11:15:43