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

Grails编写简单单元测试遇问题:User对象未创建

问题分析与解决方案

你遇到的问题核心在于Grails单元测试中mockDomain的特性限制:默认情况下调用save()仅验证对象约束,不会自动将对象存入模拟内存数据库;同时自定义命名查询notDeleted也需要额外处理才能在测试环境中正常工作。下面是具体的修复步骤:

1. 确保保存的对象被加入模拟存储

当用mockDomain模拟领域类后,save()方法只负责校验对象合法性,不会自动把对象加入模拟的持久化上下文。你需要手动调用addToInstances方法将保存后的对象加入到User的模拟实例列表中:

setup:
def savedUser = new User(
    username: "username",
    googleId: "googleId",
    email: "user@domain.com",
    enabled: true,
    accountExpired: false,
    accountLocked: false,
    passwordExpired: false
).save(failOnError: true)
User.addToInstances(savedUser) // 关键:将对象注入模拟存储

2. 处理命名查询notDeleted的模拟

mockDomain不会自动解析并模拟命名查询的逻辑,所以User.notDeleted.findByGoogleId(googleId)在测试中无法正确匹配到对象。你有两种可行的解决方式:

方式一:手动模拟命名查询行为

在测试的初始化阶段,给User类动态添加notDeleted方法的模拟实现:

def setupSpec() {
    mockDomain User
    // 模拟notDeleted命名查询,匹配deletedAt为null的用户
    User.metaClass.static.notDeleted = { ->
        User.createCriteria().build {
            isNull 'deletedAt'
        }
    }
}

方式二:替换命名查询为Where查询(更推荐)

在UserService中把命名查询换成Grails的Where查询,这种方式在单元测试中更容易被模拟支持:

@Transactional(readOnly = true)
User getByGoogleId(String googleId) {
    User.where {
        deletedAt == null && googleId == googleIdParam
    }.find([googleIdParam: googleId])
}

3. 改用DataTest的persist方法(更简洁)

你的测试类已经实现了DataTest接口,可以直接使用它提供的persist方法保存对象,它会自动处理模拟存储的注入,无需手动调用addToInstances:

setup:
persist(new User(
    username: "username",
    googleId: "googleId",
    email: "user@domain.com",
    enabled: true,
    accountExpired: false,
    accountLocked: false,
    passwordExpired: false
))

完整修复后的测试代码示例

class UserServiceSpec extends Specification implements ServiceUnitTest<UserService>, DataTest {

    def setupSpec() {
        mockDomain User
        // 模拟notDeleted命名查询
        User.metaClass.static.notDeleted = { ->
            User.createCriteria().build {
                isNull 'deletedAt'
            }
        }
    }

    void "getByGoogleId should return the correct non-deleted user"() {
        setup:
        persist(new User(
            username: "username",
            googleId: "googleId",
            email: "user@domain.com",
            enabled: true,
            accountExpired: false,
            accountLocked: false,
            passwordExpired: false
        ))

        when:
        def user = service.getByGoogleId("googleId")

        then:
        User.count() == 1
        user.username == "username"
    }
}

补充说明:因为你的User类继承了AuditableEntity,deletedAt字段默认是null,完全符合notDeleted的查询条件,所以测试对象不需要额外设置该字段。

内容的提问来源于stack exchange,提问作者Alex A.

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.29 08:12:02