我有以下测试(可能更多是功能测试而不是集成但是......):
@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
调用时,该数据就不存在了。
完全有可能我不应该这样做这样的测试,所以建议值得赞赏。
答案 0 :(得分:4)
这绝对是一项功能测试 - 您正在针对您的服务器发出HTTP请求,而不是进行方法调用,然后对这些调用的效果进行断言。
您无法通过功能测试获得自动回滚,因为无论测试是否在与服务器相同的JVM中运行,调用都在一个线程中进行,而在另一个线程中进行处理。 BootStrap中的代码在所有测试运行并提交之前运行一次(因为您在事务中进行了更改或通过自动提交),然后是'客户端'测试代码在其自己的新Hibernate会话和测试基础架构启动的事务中运行(并将在测试方法结束时回滚),服务器端代码在其自己的Hibernate会话中运行(因为OSIV)和取决于您的控制器和服务是否具有交易性,可以在不同的交易中运行,也可以只是自动提交。
这可能不是一个因素,但应该始终考虑使用Hibernate持久性测试是会话缓存。 Hibernate会话是第一级缓存,像findByName
这样的动态查找器可能会触发你想要的刷新,但在测试中应该是明确的。但是它不会清除任何缓存的元素,你会冒这样的代码误报的风险,因为你可能实际上并没有加载一个新实例--Hibernate可能会返回一个缓存的实例。在调用get()
时肯定会。我总是在集成和功能基类中添加flushAndClear()
方法,并且在put
块中的when
调用之后调用它,以确保所有内容都从Hibernate到数据库(未提交,只是刷新)并清除会话以强制实际重新加载。只需选择一个随机域类并使用withSession
,例如
protected void flushAndClear() {
Foo.withSession { session ->
session.flush()
session.clear()
}
}
由于put
发生在一个线程/会话/ tx中并且查找程序自行运行,因此这不应该有效,但应该是一般使用的模式。