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.
相关产品推荐
相关产品推荐

