Scala 规范2中模拟slick.dbio.dbio组成

Scala 规范2中模拟slick.dbio.dbio组成,scala,transactions,slick,specs2,Scala,Transactions,Slick,Specs2,使用Scala,Play框架,Slick 3,Specs2 我有一个存储库层和一个服务层。存储库非常愚蠢,我使用specs2来确保服务层完成它的工作 我的存储库用于返回未来,如下所示: def findById(id: Long): Future[Option[Foo]] = db.run(fooQuery(id)) fooRepository.findById(3).returns(Future(Foo(3))) val findFooDbio = DBIO.successful(No

使用Scala,Play框架,Slick 3,Specs2

我有一个存储库层和一个服务层。存储库非常愚蠢,我使用
specs2
来确保服务层完成它的工作

我的存储库用于返回未来,如下所示:

def findById(id: Long): Future[Option[Foo]] =
  db.run(fooQuery(id))
fooRepository.findById(3).returns(Future(Foo(3)))
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))
然后将在服务中使用:

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
将被模拟如下:

def findById(id: Long): Future[Option[Foo]] =
  db.run(fooQuery(id))
fooRepository.findById(3).returns(Future(Foo(3)))
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))

我最近发现需要数据库事务。应将多个查询合并到单个事务中。问题似乎是,在服务层处理事务逻辑是完全可以的

考虑到这一点,我将我的存储库更改为return
slick.dbio.dbio
,并添加了一个helper方法以事务方式运行查询:

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)
}
这似乎有效,但现在我只能这样描述:

def findById(id: Long): Future[Option[Foo]] =
  db.run(fooQuery(id))
fooRepository.findById(3).returns(Future(Foo(3)))
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))
运行中的
任何
都很糟糕!现在我不测试实际逻辑,而是接受任何
DBIO[Foo]
!我尝试使用以下方法:

fooRepository.run(findFooDbio.flatMap(_ => saveFooDbio)) returns Future(Foo(3))
但它与
java.lang.NullPointerException:null
,这是
specs2
表示“抱歉,伙计,找不到具有此参数的方法”的方式。我试过各种各样的变体,但都不管用

我怀疑这可能是因为无法比较函数:

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
scala>vala:Int=>String=x=>“嗨”
a:Int=>String=
scala>valb:Int=>String=x=>hi
b:Int=>String=
scala>a==b
res1:Boolean=false

你知道如何在不作弊的情况下规范DBIO的组成吗?

我有一个类似的想法,并对其进行了调查,看看是否可以:

  • 创建相同的DBIO组合
  • 在模拟中与matcher一起使用
然而,我发现这实际上是不可行的:

  • 正如您所注意到的,您无法比较函数
  • 此外,当您研究DBIO的内部结构时,它基本上是一种类似于monad的自由结构——它实现了普通值和直接生成的查询(如果可以比较查询的某些部分的话),但也有存储函数的映射
  • 即使您以某种方式设法重用函数,使它们具有引用相等性,DBIO的实现也不关心重写
    equals
    ,因此它们无论如何都是不同的
知道了这一点,我放弃了最初的想法。我可以推荐:

  • 数据库的任何输入进行模拟。运行
    -它更容易出错,因为如果测试期望开始与返回结果不同,它不会通知您,但总比什么都没有好
  • 用一些中间结构替换DBIO,您知道可以安全地进行比较。例如,Cats使用case类,只要您设法确保函数在某种程度上具有可比性(例如,通过不临时创建它们,而是使用
    val
    s和
    object
    s),您就可以在中间表示上进行比较,并模拟整个解释->运行过程
  • 用模拟数据库替换单元测试,用实际数据库替换集成测试
  • 尝试处理数据库的模式-基本上在测试中注入与生产中不同的monad(例如,
    prod->service
    返回DBIO,
    production->service
    返回您想要的未来)

实际上,您可以尝试使用免费、TTFI和交换实现的许多其他功能。底线是——您无法在DBIO上进行可靠的比较,因此,请以一种不用这样做就可以进行测试的方式设计代码。这不是一个令人愉快的答案,尤其是如果你只是想把测试放在一起,然后继续前进,但恐怕没有其他办法了。

答案很好,值得等待:)