使用sqlmock测试GORM多INSERT操作时遇错误求排查
问题定位与解决方案
错误原因
你只定义了2个ExpectPrepare,但业务逻辑要执行3次INSERT操作,第三次调用时没有匹配的Prepare预期,直接触发sqlmock的未预期操作报错。另外,GORM调用Create方法时,内部会先执行PREPARE生成预编译SQL,再执行EXEC完成插入——每一次Create调用都会对应一套完整的PREPARE + EXEC流程,三次循环就需要三次对应的预期,或者配置允许复用Prepare对象。
解决方案1:复用Prepare对象(推荐)
使用sqlmock的WillReusePrepare()方法,让同一个Prepare可以被多次执行,只需要定义一次Prepare,再对应三次Exec预期即可:
func mockDatabase(t *testing.T) (*gorm.DB, sqlmock.Sqlmock) { db, mock, err := sqlmock.New() if err != nil { t.Fatalf("failed to open sqlmock database: %v", err) } gormDB, err := gorm.Open(mysql.New(mysql.Config{ Conn: db, SkipInitializeWithVersion: true, }), &gorm.Config{}) if err != nil { t.Fatalf("failed to open gorm database: %v", err) } // 定义一次Prepare,允许复用 mock.ExpectPrepare(`^INSERT INTO \`TABLE\`(.+)$`).WillReusePrepare() // 添加三次Exec预期,参数替换为实际业务数据 for i := 0; i < 3; i++ { mock.ExpectExec(`^INSERT INTO \`TABLE\`(.+)$`). WithArgs("param1_value", "param2_value"). WillReturnResult(sqlmock.NewResult(1, 1)) } return gormDB, mock }
解决方案2:显式定义三次Prepare+Exec预期
如果不想复用Prepare,可以直接循环三次,每次都定义对应的Prepare和Exec:
func mockDatabase(t *testing.T) (*gorm.DB, sqlmock.Sqlmock) { db, mock, err := sqlmock.New() if err != nil { t.Fatalf("failed to open sqlmock database: %v", err) } gormDB, err := gorm.Open(mysql.New(mysql.Config{ Conn: db, SkipInitializeWithVersion: true, }), &gorm.Config{}) if err != nil { t.Fatalf("failed to open gorm database: %v", err) } // 循环三次,每次定义Prepare和对应的Exec for i := 0; i < 3; i++ { mock.ExpectPrepare(`^INSERT INTO \`TABLE\`(.+)$`) mock.ExpectExec(`^INSERT INTO \`TABLE\`(.+)$`). WithArgs("param1_value", "param2_value"). WillReturnResult(sqlmock.NewResult(1, 1)) } return gormDB, mock }
额外注意事项
- 确保
WithArgs中的参数顺序和GORM生成的SQL参数顺序完全一致,GORM会按照结构体字段顺序(或tag指定的顺序)生成参数,顺序不匹配也会触发未预期错误。 - 测试结束后务必调用
mock.ExpectationsWereMet(),验证所有预期操作都被执行。
内容的提问来源于stack exchange,提问作者Carlos Benjumea
相关产品推荐
相关产品推荐

