GORM软删除sqlmock测试UPDATE语句匹配失败报错排查
GORM软删除单元测试sqlmock匹配失败问题
问题场景
编写Workspace软删除逻辑的单元测试时,其余CRUD测试均可正常运行,仅软删除触发的UPDATE语句mock校验不通过。
相关代码
单元测试用例
func TestDeleteWorkspace(t *testing.T) { conn, mock, repository, err := setup() defer conn.Close() assert.NoError(t, err) uid := uuid.New() // mock.ExpectBegin() mock.ExpectQuery(regexp.QuoteMeta(`UPDATE "workspaces" SET`)).WithArgs(sqlmock.AnyArg(), uid) // mock.ExpectCommit() var e bool e, err = repository.Delete(uid) assert.NoError(t, err) assert.True(t, e) err = mock.ExpectationsWereMet() assert.NoError(t, err) }
仓储层Delete实现
func (r *WorkspaceRepository) Delete(id any) (bool, error) { if err := r.db.Delete(&model.Workspace{}, "id = ?", id).Error; err != nil { return false, nil } return true, nil }
Workspace模型定义
模型使用GORM内置软删除特性:
type Workspace struct { ID uuid.UUID `gorm:"type:uuid;default:uuid_generate_v4()" json:"id"` Name string `gorm:"not null,type:text" json:"name"` CreatedAt time.Time `gorm:"autoCreateTime" json:"create_time"` UpdatedAt time.Time `gorm:"autoUpdateTime" json:"update_time"` DeletedAt gorm.DeletedAt `gorm:"index,->" json:"-"` }
软删除实际生成的SQL语句:
UPDATE "workspaces" SET "deleted_at"='2022-07-04 09:09:20.778' WHERE id = 'c4610193-b43a-4ed7-9ed6-9d67b3f97502' AND "workspaces"."deleted_at" IS NULL
报错信息
测试运行返回错误:
workspace_test.go:169: Error Trace: workspace_test.go:169 Error: Received unexpected error: there is a remaining expectation which was not matched: ExpectedQuery => expecting Query, QueryContext or QueryRow which: - matches sql: 'UPDATE "workspaces" SET' - is with arguments: 0 - 28e7aa46-7a22-4dc7-b3ce-6cf02af525ca 1 - {}
根因分析
mock配置存在3处错误:
- 匹配方法类型错误:
UPDATE/DELETE/INSERT这类写操作,GORM会调用驱动的Exec接口执行,ExpectQuery仅能匹配SELECT这类走Query接口的读操作,因此定义的期望始终无法被命中,最终提示存在未匹配的剩余期望。 - 事务流程期望缺失:GORM默认对所有写操作开启隐式事务,注释掉
ExpectBegin()和ExpectCommit()会导致事务流程校验不通过。 - 参数匹配数量不足:GORM自动拼接的
deleted_at IS NULL软删除条件会额外携带一个空值参数,仅配置2个参数的匹配规则无法覆盖全部入参。
修复方案
调整测试用例的mock配置即可:
func TestDeleteWorkspace(t *testing.T) { conn, mock, repository, err := setup() defer conn.Close() assert.NoError(t, err) uid := uuid.New() // 补全事务期望 mock.ExpectBegin() // 写操作改用ExpectExec匹配,补充参数匹配规则,定义执行返回结果 mock.ExpectExec(regexp.QuoteMeta(`UPDATE "workspaces" SET`)). WithArgs(sqlmock.AnyArg(), uid, sqlmock.AnyArg()). WillReturnResult(sqlmock.NewResult(1, 1)) mock.ExpectCommit() var e bool e, err = repository.Delete(uid) assert.NoError(t, err) assert.True(t, e) err = mock.ExpectationsWereMet() assert.NoError(t, err) }
配置说明:动态生成的时间值、软删除条件携带的空值都用
sqlmock.AnyArg()匹配即可,不需要硬编码固定值,可兼容不同GORM版本的参数生成逻辑。
内容的提问来源于stack exchange,提问作者Rodrigo
相关产品推荐
相关产品推荐

