在 specs2 中模拟 slick.dbio.DBIO 组合
Mocking slick.dbio.DBIO composition in specs2
使用 Scala、Play Framework、Slick 3、Specs2。
我有一个 repository 层和一个 service 层。存储库非常愚蠢,我使用 specs2
来确保服务层完成它的工作。
我的存储库用于 return 期货,像这样:
def findById(id: Long): Future[Option[Foo]] =
db.run(fooQuery(id))
然后在服务中使用:
def foonicate(id: Long): Future[Foo] = {
fooRepository.findById(id).flatMap { optFoo =>
val foo: Foo = optFoo match {
case Some(foo) => [business logic returning Foo]
case None => [business logic returning Foo]
}
fooRepository.save(foo)
}
}
服务很容易规范。在服务规范中,FooRepository
会被这样模拟:
fooRepository.findById(3).returns(Future(Foo(3)))
我最近发现需要数据库事务。多个查询应该合并到一个事务中。 prevailing opinion 似乎是在服务层处理事务逻辑是完全可以的。
考虑到这一点,我已将我的存储库更改为 return slick.dbio.DBIO
并向 运行 事务查询添加了一个辅助方法:
def findById(id: Long): DBIO[Option[Foo]] =
fooQuery(id)
def run[T](query: DBIO[T]): Future[T] =
db.run(query.transactionally)
服务组成 DBIO
s 并最终调用存储库以 运行 查询:
def foonicate(id: Long): Future[Foo] = {
val query = fooRepository.findById(id).flatMap { optFoo =>
val foo: Foo = optFoo match {
case Some(foo) => [business logic finally returning Foo]
case None => [business logic finally returning Foo]
}
fooRepository.save(foo)
}
fooRepository.run(query)
}
这似乎可行,但现在我只能这样指定它:
val findFooDbio = DBIO.successful(None))
val saveFooDbio = DBIO.successful(Foo(3))
fooRepository.findById(3) returns findFooDbio
fooRepository.save(Foo(3)) returns saveFooDbio
fooRepository.run(any[DBIO[Foo]]) returns Future(Foo(3))
run
模拟中的 any
太烂了!现在我不是在测试实际的逻辑,而是接受任何 DBIO[Foo]
!我尝试使用以下内容:
fooRepository.run(findFooDbio.flatMap(_ => saveFooDbio)) returns Future(Foo(3))
但它与 java.lang.NullPointerException: null
不同,这是 specs2
的说法 "sorry mate, the method with this parameter wasn't found"。我尝试了各种变体,但其中 none 有效。
我怀疑这可能是因为无法比较函数:
scala> val a: Int => String = x => "hi"
a: Int => String = <function1>
scala> val b: Int => String = x => "hi"
b: Int => String = <function1>
scala> a == b
res1: Boolean = false
关于如何在不作弊的情况下指定 DBIO 组合的任何想法?
我也有类似的想法,也研究了一下是否可以:
- 创建相同的 DBIO 组合
- 在模拟中与匹配器一起使用
但是我发现实际上在实践中是行不通的:
- 如您所见,您无法比较函数
- 此外,当您调查 DBIO 的内部结构时,它基本上是 Free-monad-like 结构 - 它具有普通值和直接生成的查询的实现(然后,如果 you extract the statements 您可以比较 some 查询的一部分),但也有存储函数的映射
- 即使您以某种方式设法重用函数,使它们具有引用相等性,DBIO 的实现也不关心重写
equals
,所以它们无论如何都是不同的野兽
所以知道后,我放弃了最初的想法。我可以推荐的是:
- 模拟
database.run
的任何输入 - 它更容易出错,因为如果测试预期开始与返回结果不同,它不会通知您,但总比没有好
- 用一些你知道你可以安全比较的中间结构替换 DBIO。例如。 Cats 的 Free monad implementation 使用 case 类 所以只要你设法确保函数在某种程度上是可比较的(例如,不是临时创建它们,而是使用
val
s 和 object
s),你可以比较中间表示,并模拟整个 interpret -> 运行 process
- 用实际数据库的集成测试替换模拟数据库的单元测试
- 尝试 Typed Tagless Final Interpreter 处理数据库的模式 - 基本上在测试中注入与生产中不同的 monad(例如
prod -> service
返回 DBIO,production -> service
返回你想要的 Futures)
实际上,您可以通过免费、TTFI 和交换实现来尝试许多其他事情。底线是——你不能可靠地比较 DBIO,所以设计你的代码,你可以在不这样做的情况下进行测试。这不是一个令人愉快的答案,特别是如果你只是想把测试放在一起,然后继续,但据我所知,没有其他办法。
使用 Scala、Play Framework、Slick 3、Specs2。
我有一个 repository 层和一个 service 层。存储库非常愚蠢,我使用 specs2
来确保服务层完成它的工作。
我的存储库用于 return 期货,像这样:
def findById(id: Long): Future[Option[Foo]] =
db.run(fooQuery(id))
然后在服务中使用:
def foonicate(id: Long): Future[Foo] = {
fooRepository.findById(id).flatMap { optFoo =>
val foo: Foo = optFoo match {
case Some(foo) => [business logic returning Foo]
case None => [business logic returning Foo]
}
fooRepository.save(foo)
}
}
服务很容易规范。在服务规范中,FooRepository
会被这样模拟:
fooRepository.findById(3).returns(Future(Foo(3)))
我最近发现需要数据库事务。多个查询应该合并到一个事务中。 prevailing opinion 似乎是在服务层处理事务逻辑是完全可以的。
考虑到这一点,我已将我的存储库更改为 return slick.dbio.DBIO
并向 运行 事务查询添加了一个辅助方法:
def findById(id: Long): DBIO[Option[Foo]] =
fooQuery(id)
def run[T](query: DBIO[T]): Future[T] =
db.run(query.transactionally)
服务组成 DBIO
s 并最终调用存储库以 运行 查询:
def foonicate(id: Long): Future[Foo] = {
val query = fooRepository.findById(id).flatMap { optFoo =>
val foo: Foo = optFoo match {
case Some(foo) => [business logic finally returning Foo]
case None => [business logic finally returning Foo]
}
fooRepository.save(foo)
}
fooRepository.run(query)
}
这似乎可行,但现在我只能这样指定它:
val findFooDbio = DBIO.successful(None))
val saveFooDbio = DBIO.successful(Foo(3))
fooRepository.findById(3) returns findFooDbio
fooRepository.save(Foo(3)) returns saveFooDbio
fooRepository.run(any[DBIO[Foo]]) returns Future(Foo(3))
run
模拟中的 any
太烂了!现在我不是在测试实际的逻辑,而是接受任何 DBIO[Foo]
!我尝试使用以下内容:
fooRepository.run(findFooDbio.flatMap(_ => saveFooDbio)) returns Future(Foo(3))
但它与 java.lang.NullPointerException: null
不同,这是 specs2
的说法 "sorry mate, the method with this parameter wasn't found"。我尝试了各种变体,但其中 none 有效。
我怀疑这可能是因为无法比较函数:
scala> val a: Int => String = x => "hi"
a: Int => String = <function1>
scala> val b: Int => String = x => "hi"
b: Int => String = <function1>
scala> a == b
res1: Boolean = false
关于如何在不作弊的情况下指定 DBIO 组合的任何想法?
我也有类似的想法,也研究了一下是否可以:
- 创建相同的 DBIO 组合
- 在模拟中与匹配器一起使用
但是我发现实际上在实践中是行不通的:
- 如您所见,您无法比较函数
- 此外,当您调查 DBIO 的内部结构时,它基本上是 Free-monad-like 结构 - 它具有普通值和直接生成的查询的实现(然后,如果 you extract the statements 您可以比较 some 查询的一部分),但也有存储函数的映射
- 即使您以某种方式设法重用函数,使它们具有引用相等性,DBIO 的实现也不关心重写
equals
,所以它们无论如何都是不同的野兽
所以知道后,我放弃了最初的想法。我可以推荐的是:
- 模拟
database.run
的任何输入 - 它更容易出错,因为如果测试预期开始与返回结果不同,它不会通知您,但总比没有好 - 用一些你知道你可以安全比较的中间结构替换 DBIO。例如。 Cats 的 Free monad implementation 使用 case 类 所以只要你设法确保函数在某种程度上是可比较的(例如,不是临时创建它们,而是使用
val
s 和object
s),你可以比较中间表示,并模拟整个 interpret -> 运行 process - 用实际数据库的集成测试替换模拟数据库的单元测试
- 尝试 Typed Tagless Final Interpreter 处理数据库的模式 - 基本上在测试中注入与生产中不同的 monad(例如
prod -> service
返回 DBIO,production -> service
返回你想要的 Futures)
实际上,您可以通过免费、TTFI 和交换实现来尝试许多其他事情。底线是——你不能可靠地比较 DBIO,所以设计你的代码,你可以在不这样做的情况下进行测试。这不是一个令人愉快的答案,特别是如果你只是想把测试放在一起,然后继续,但据我所知,没有其他办法。