我有以下测试(这可能更像是功能测试而不是集成但是......):
@Integration(applicationClass = Application)
@Rollback
class ConventionControllerIntegrationSpec extends Specification {
RestBuilder rest = new RestBuilder()
String url
def setup() {
url = "http://localhost:${serverPort}/api/admin/organizations/${Organization.first().id}/conventions"
}
def cleanup() {
}
void "test update convention"() {
given:
Convention convention = Convention.first()
when:
RestResponse response = rest.put("${url}/${convention.id}") {
contentType "application/json"
json {
name = "New Name"
}
}
then:
response.status == HttpStatus.OK.value()
Convention.findByName("New Name").id == convention.id
Convention.findByName("New Name").name == "New Name"
}
}
数据正在通过 BootStrap 加载(诚然这可能是问题所在)但问题是当我在 then
block 中时;它通过新名称和 id
匹配找到 Convention
,但是在测试 name
字段时,它失败了,因为它仍然有旧的姓名。
在阅读有关测试的文档时,我认为问题在于数据是在哪个 session 中创建的。由于 @Rollback
有一个与 BootStrap
分开的 session ,数据并没有真正凝固。例如,如果我通过测试的 given
block 加载数据,那么当我的 Controller 被 RestBuilder
调用时,该数据不存在。
我完全有可能不应该以这种方式进行此类测试,因此不胜感激。
最佳答案
这绝对是一个功能测试 - 您正在对您的服务器发出 HTTP 请求,而不是进行方法调用,然后对这些调用的效果进行断言。
您无法通过功能测试获得自动回滚,因为调用是在一个线程中进行的,而它们是在另一个线程中处理的,无论测试是否在与服务器相同的 JVM 中运行。 BootStrap 中的代码在所有测试运行并提交之前运行一次(因为您在事务中进行了更改或通过自动提交),然后“客户端”测试代码在其自己的新 Hibernate session 和事务中运行测试基础架构启动(并将在测试方法结束时回滚),并且服务器端代码在其自己的 Hibernate session 中运行(由于 OSIV)并且取决于您的 Controller 和服务是否是事务性与否,可能在不同的事务中运行,或者可能只是自动提交。
有一件事可能不是这里的一个因素,但在 Hibernate 持久性测试中应该始终考虑的是 session 缓存。 Hibernate session 是一级缓存,像 findByName
这样的动态查找器可能会触发刷新,这是您想要的,但应该在测试中明确说明。但是它不会清除任何缓存的元素,并且您使用这样的代码会冒误报的风险,因为您实际上可能没有加载新实例——Hibernate 可能会返回一个缓存的实例。调用 get()
时肯定会。我总是将 flushAndClear()
方法添加到集成和功能基类中,并且我会在 when
中的 put
调用之后调用该方法> 阻止以确保所有内容都从 Hibernate 刷新到数据库(未提交,只是刷新)并清除 session 以强制真正重新加载。只需选择一个随机域类并使用 withSession
,例如
protected void flushAndClear() {
Foo.withSession { session ->
session.flush()
session.clear()
}
}
由于 put
发生在一个线程/ session /tx 中,并且发现者在自己的运行中,这应该不会有影响,但应该是通常使用的模式。
关于Grails 3 集成规范具有奇怪的事务行为,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/40443386/