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

Grails 4.0.3集成测试疑问:setup()调用外部方法保存的数据无法持久化是Bug还是预期行为?

这是Grails集成测试的预期行为,而非框架Bug

你碰到的这个差异,根源在于Spring(Grails基于Spring构建)的事务代理机制,以及GORM的会话管理逻辑,咱们一步步理清楚:

核心原理:Spring AOP代理的"内部调用限制"

Grails的@Integration测试配合@Rollback注解时,默认会为每个测试方法(包括setup和cleanup)创建一个独立的事务,测试完成后自动回滚。但Spring的事务增强是靠动态代理实现的——只有通过代理对象调用的方法才会触发事务拦截逻辑,同一个类内部的直接方法调用,是不会经过代理层的,自然也无法继承当前的事务上下文。

为什么第一个测试用例会失败?

看你第一个测试用例的代码:

def setup() {
    createUser('Daonald')
    assert User.findByName('Daonald') // passes
}
def createUser(String name) {
    User user = new User(name: name).save(failOnError: true, flush: true)
}
  • setup()本身运行在测试事务的上下文里,但你直接调用createUser()属于类内部方法调用,跳过了Spring的事务代理。
  • 当createUser()执行save(flush: true)时,因为没有可继承的代理事务,GORM会为这个save操作创建一个临时的独立事务,并且在方法执行完毕后自动回滚(因为没有外部事务管理)。
  • setup()里的assert能通过,是因为flush: true把数据刷到了当前的Hibernate会话中,但这个数据并没有真正提交到数据库;当createUser()方法结束,临时事务回滚,会话里的脏数据被清理。
  • 到了测试方法执行时,测试事务的会话里已经没有这条数据了,所以User.findByName('Daonald')会返回null,导致assert失败。

为什么第二个测试用例能通过?

再看第二个测试用例:

def setup() {
    User user = new User(name: 'Daonald').save(failOnError: true, flush: true)
    assert User.findByName('Daonald') // passes
}

这里的save操作直接在setup()内部执行,而setup()是被测试框架通过代理调用的,所以整个操作都处于测试事务的上下文里。flush: true只是把数据刷到当前会话,并没有提交事务——测试方法执行时,依然在同一个事务会话中,自然能查到这条数据,直到测试结束后整个事务被@Rollback回滚。

如何解决第一个测试用例的问题?

如果想要在setup()里调用自定义方法保存数据,同时让数据能在测试方法中访问,可以用以下两种方式:

  • 把createUser()方法提取到单独的服务类中:通过依赖注入调用服务类的方法,这样会经过Spring代理,事务上下文会被正确继承。
  • 手动获取代理对象调用自身方法:在测试类中注入ApplicationContext,通过applicationContext.getBean(getClass())获取自身的代理对象,然后用代理对象调用createUser()方法。

举个用第一种方法实现的示例:

// 先创建一个UserService
@Service
class UserService {
    User createUser(String name) {
        new User(name: name).save(failOnError: true, flush: true)
    }
}

// 测试类中注入服务
@Integration @Rollback
class MySpec extends Specification {
    UserService userService

    def setup() {
        userService.createUser('Daonald')
        assert User.findByName('Daonald')
    }

    void 'data persists now'() {
        expect:
        User.findByName('Daonald') != null
    }
}

内容的提问来源于stack exchange,提问作者LazyMap

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.29 14:02:38