Golang squirrel含VALUES子句查询的sqlmock单元测试匹配问题
问题根因
Go 原生 map 的遍历顺序是随机的,直接遍历 map 生成 squirrel 的 VALUES 行数据时,每次输出的行顺序都不固定,sqlmock 默认按固定字符串/正则规则匹配 SQL,自然无法命中预期。
解决方案
优先选第一种方案,实现成本最低,维护性最好。
方案1:构建查询时固定遍历顺序(推荐)
在读取 map 数据拼接 VALUES 前,先对 map 的 key 做排序,按固定顺序遍历生成行数据,就能保证每次生成的 SQL 中 VALUES 子句的行顺序完全一致,单测直接写死预期 SQL 即可。
示例代码:
import ( "sort" "github.com/Masterminds/squirrel" ) func BuildCountryRegionQuery(codeMap map[string]string) squirrel.SelectBuilder { // 提取map所有key keys := make([]string, 0, len(codeMap)) for k := range codeMap { keys = append(keys, k) } // 按固定规则排序,这里用字典序,可根据业务需求调整排序逻辑 sort.Strings(keys) // 按排序后的顺序生成VALUES行 valueRows := make([]interface{}, 0, len(keys)) for _, k := range keys { valueRows = append(valueRows, squirrel.Expr("(?,?)", k, codeMap[k])) } // 拼接最终查询,PostgreSQL记得用Dollar占位符格式 return squirrel.StatementBuilder.PlaceholderFormat(squirrel.Dollar). Select("country_code", "region_code"). FromValues(valueRows, "country_code", "region_code") }
注意:如果业务本身对 VALUES 的行顺序有明确要求,直接按业务规则排序即可,不需要额外加字典序排序,保证单测顺序和业务逻辑顺序一致就行。字典序排序的性能开销极低,即使map有上百个key,对整体查询性能的影响也可以忽略不计。
方案2:自定义sqlmock匹配规则(业务不要求行顺序时用)
如果业务层面不需要固定 VALUES 的行顺序,就不要硬改生产代码的逻辑,直接给 sqlmock 写自定义匹配器,跳过顺序校验,只校验 SQL 结构和参数集合是否符合预期。
示例代码:
import ( "reflect" "strings" "testing" "github.com/DATA-DOG/go-sqlmock" ) func TestBuildQuery(t *testing.T) { db, mock, err := sqlmock.New() if err != nil { t.Fatalf("init mock error: %v", err) } defer db.Close() expectedMap := map[string]string{"us":"us", "ca":"ca", "se":"eu"} // 自定义SQL匹配逻辑 mock.ExpectQuery(func(actualSQL string) bool { // 先校验SQL基础结构 if !strings.Contains(actualSQL, "FROM (VALUES") { return false } // 自行实现解析逻辑,提取actualSQL中VALUES里的所有值对,转成和expectedMap同结构的集合 actualPairs := parseValuesPairs(actualSQL) // 无序对比两个集合是否完全一致 return reflect.DeepEqual(actualPairs, expectedMap) }).WillReturnRows(sqlmock.NewRows([]string{"country_code", "region_code"})) // 执行目标查询逻辑 // ... }
这种方案不需要改生产代码,但是需要写简单的SQL片段解析逻辑,适合对VALUES顺序无要求的场景。
内容的提问来源于stack exchange,提问作者ANANDA KRISHNAN
相关产品推荐
相关产品推荐

