Grails 3 集成规范具有奇怪的事务行为
Grails 3 Integration Spec has Strange Transactional Behavior
我有以下测试(这可能更像是功能测试而不是集成但是...):
@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
块时;它通过新名称找到 Convention
并且 id
匹配,但是在测试 name
字段时,它失败了,因为它仍然具有旧名称。
在阅读有关测试的文档时,我认为问题在于数据是在哪个会话中创建的。由于 @Rollback
有一个与 BootStrap
分开的会话,因此数据不是真的胶凝。例如,如果我通过测试的 given
块加载数据,那么当 RestBuilder
调用我的控制器时该数据不存在。
我完全有可能不应该以这种方式进行此类测试,因此不胜感激。
这绝对是一个功能测试 - 您正在对您的服务器发出 HTTP 请求,而不是进行方法调用,然后对这些调用的效果进行断言。
您无法通过功能测试获得自动回滚,因为调用是在一个线程中进行的,而它们是在另一个线程中处理的,无论测试 运行 是否与服务器位于同一 JVM 中。 BootStrap 运行 中的代码在所有测试 运行 之前一次并被提交(因为您在事务中进行了更改或通过自动提交),然后 'client' 测试代码 运行s 在它自己的新 Hibernate 会话和测试基础设施启动的事务中(并将在测试方法结束时回滚),以及服务器端代码 运行s在它自己的 Hibernate 会话中(由于 OSIV)并且取决于您的控制器和服务是否是事务性的,可能 运行 在不同的事务中,或者可能只是自动提交。
有一件事可能不是这里的一个因素,但在 Hibernate 持久性测试中应该始终考虑的是会话缓存。 Hibernate 会话是一级缓存,像 findByName
这样的动态查找器可能会触发刷新,这是您想要的,但应该在测试中明确说明。但是它不会清除任何缓存的元素,并且您 运行 使用这样的代码存在误报的风险,因为您实际上可能没有加载新实例 - Hibernate 可能会返回一个缓存的实例。调用 get()
时肯定会。我总是在集成和功能基础 classes 中添加一个 flushAndClear()
方法,并且我会在 when
块中的 put
调用之后调用它以确保一切从 Hibernate 刷新到数据库(未提交,只是刷新)并清除会话以强制真正重新加载。只需选择一个随机域 class 并使用 withSession
,例如
protected void flushAndClear() {
Foo.withSession { session ->
session.flush()
session.clear()
}
}
由于 put
出现在一个 thread/session/tx 中,发现者 运行 出现在他们自己身上,这应该不会有影响,但应该是通常使用的模式。
我有以下测试(这可能更像是功能测试而不是集成但是...):
@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
块时;它通过新名称找到 Convention
并且 id
匹配,但是在测试 name
字段时,它失败了,因为它仍然具有旧名称。
在阅读有关测试的文档时,我认为问题在于数据是在哪个会话中创建的。由于 @Rollback
有一个与 BootStrap
分开的会话,因此数据不是真的胶凝。例如,如果我通过测试的 given
块加载数据,那么当 RestBuilder
调用我的控制器时该数据不存在。
我完全有可能不应该以这种方式进行此类测试,因此不胜感激。
这绝对是一个功能测试 - 您正在对您的服务器发出 HTTP 请求,而不是进行方法调用,然后对这些调用的效果进行断言。
您无法通过功能测试获得自动回滚,因为调用是在一个线程中进行的,而它们是在另一个线程中处理的,无论测试 运行 是否与服务器位于同一 JVM 中。 BootStrap 运行 中的代码在所有测试 运行 之前一次并被提交(因为您在事务中进行了更改或通过自动提交),然后 'client' 测试代码 运行s 在它自己的新 Hibernate 会话和测试基础设施启动的事务中(并将在测试方法结束时回滚),以及服务器端代码 运行s在它自己的 Hibernate 会话中(由于 OSIV)并且取决于您的控制器和服务是否是事务性的,可能 运行 在不同的事务中,或者可能只是自动提交。
有一件事可能不是这里的一个因素,但在 Hibernate 持久性测试中应该始终考虑的是会话缓存。 Hibernate 会话是一级缓存,像 findByName
这样的动态查找器可能会触发刷新,这是您想要的,但应该在测试中明确说明。但是它不会清除任何缓存的元素,并且您 运行 使用这样的代码存在误报的风险,因为您实际上可能没有加载新实例 - Hibernate 可能会返回一个缓存的实例。调用 get()
时肯定会。我总是在集成和功能基础 classes 中添加一个 flushAndClear()
方法,并且我会在 when
块中的 put
调用之后调用它以确保一切从 Hibernate 刷新到数据库(未提交,只是刷新)并清除会话以强制真正重新加载。只需选择一个随机域 class 并使用 withSession
,例如
protected void flushAndClear() {
Foo.withSession { session ->
session.flush()
session.clear()
}
}
由于 put
出现在一个 thread/session/tx 中,发现者 运行 出现在他们自己身上,这应该不会有影响,但应该是通常使用的模式。