scala - 模拟specs2中的slick.dbio.DBIO组成

标签 scala transactions slick specs2

使用 Scala、Play Framework、Slick 3、Specs2。

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

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

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看来在服务层处理事务逻辑是完全没问题的。

考虑到这一点,我更改了存储库以返回 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并最终调用存储库来运行查询:

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 的表达方式:“对不起,伙计,没有找到带有此参数的方法”。我尝试了各种变体,但没有一个起作用。

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

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 的结构 - 它具有纯值的实现和直接生成的查询(然后,如果 you extract the statements 您可以比较一些查询的一部分),但也有存储函数的映射
  • 即使您以某种方式设法重用函数,以便它们具有引用相等性,DBIO 的实现也不关心重写 equals,因此它们无论如何都会是不同的野兽

知道了这一点,我就放弃了最初的想法。我可以推荐什么:

  • 模拟database.run的任何输入 - 它更容易出错,因为如果测试期望开始与返回的结果不同,它不会通知您,但总比没有好
  • 用一些你知道可以安全比较的中间结构替换 DBIO。例如。猫Free monad implementation使用案例类,因此只要您设法确保函数在某种程度上具有可比性(例如,不临时创建它们,而是使用 valobject),您就可以可以比较中间表示,并模拟整个解释 -> 运行过程
  • 用实际数据库的集成测试替换模拟数据库的单元测试
  • 尝试 Typed Tagless Final Interpreter处理数据库的模式 - 基本上在测试中注入(inject)与生产中不同的 monad(例如 prod -> service 返回 DBIO,生产 -> service 返回您想要的 Futures)

实际上,您可以通过 Free、TTFI 和交换实现尝试许多其他事情。最重要的是 - 您无法在 DBIO 上进行可靠的比较,因此以一种无需这样做即可测试的方式设计您的代码。这不是一个令人愉快的答案,特别是如果您只想进行测试并继续前进,但据我所知没有其他办法。

关于scala - 模拟specs2中的slick.dbio.DBIO组成,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/44336658/

相关文章:

scala 的类型检查器无法识别抽象路径相关类场景中的类型

java - Java中文件操作的事务模式

mysql - 如何使用 Slick 在 VARCHAR 列中使用 UUID?

scala - 在类型别名中使用上下文绑定(bind)

Scala Actor : receiveWithin() doesn't receive messages

Scala:表达式的类型与形式参数类型不兼容

.net - 在队列之间发送时 TransactionScope 提升到 MSDTC?

python - GAE/P : Transaction safety with API calls

java - 使用 LWJGL 和 SlickUtil 显示纹理的问题

mysql jdbc 驱动程序 NumberFormatException