Play2和Scala,我应该如何配置我的集成测试以使用适当的DB运行

时间:2013-03-19 16:34:06

标签: scala testing h2 playframework-2.1 anorm

我正在试图弄清楚如何在我的Play2应用程序中编写数据库集成测试。

在我的conf文件中,我指定了两个数据库,xxx_test用于常规使用,h2 db用于测试:

db.xxx_test.driver=com.mysql.jdbc.Driver
db.xxx_test.url="jdbc:mysql://localhost/xxx_test?characterEncoding=UTF-8"
db.xxx_test.user="root"
db.xxx_test.password=""

db.h2.driver=org.h2.Driver
db.h2.url="jdbc:h2:mem:play"
db.h2.user=sa
db.h2.password=""

在我的User对象中,我指定在运行应用程序时使用xxx_test

def createUser(user: User): Option[User] = {

    DB.withConnection("xxx_test") {
      implicit connection =>
        SQL("insert into users(first_name, last_name, email, email_validated, last_login, created, modified, active) values({first_name},{last_name},{email},{email_validated},{last_login}, {created}, {modified}, {active})").on(
          'first_name -> user.firstName,
          'last_name -> user.lastName,
          'email -> user.email,
          'email_validated -> user.emailValidated,
          'last_login -> user.lastLogin,
          'created -> user.created,
          'modified -> user.modified,
          'active -> true
        ).executeInsert().map(id => {
          return Some(User(new Id[Long](id), user.firstName, user.lastName, user.email, user.emailValidated, user.lastLogin, user.created, user.modified, true))
        }
        )
    }
    None
  }

在我的测试中,我创建了一个新的inMemoryDatabase,然后使用User创建并获取我的对象 用于测试。

class DBEvolutionsTest extends Specification {

  "The Database" should {
    "persist data properly" in {
      running(FakeApplication(additionalConfiguration = inMemoryDatabase())) {

        User.create(User(Id[Long](1L), "jakob",
          "aa",
          "aaa",
          true,
          DateTime.now(),
          DateTime.now(),
          DateTime.now(),
          true)) 

        val newUser = User.findBy(Id[Long](1L))

        newUser.get.firstName must beEqualTo("jakob")
      }
      }
    }

}

这当然不是正确的方法,因为User对象使用xxx_test而不是h2 DB.withConnection("xxx_test") db。测试将在真实数据库中创建一个用户而不是内存中的用户,因为我在用户(User.create(User(...), "xxx_test"))对象中指定了数据库。 我想有一些聪明的方法可以做到这一点,我不想传递db名称 像{{1}}

这样的应用程序

你是如何解决这个问题的?

3 个答案:

答案 0 :(得分:3)

您可能想要了解如何在scala中执行依赖项注入。一个好的解决方案是从您的用户模型中抽象出数据库,然后将其作为依赖项传递。

一种简单的方法是更改​​配置文件以进行测试。 Play允许您specify which config file is used on the command line。 这不是最实用的。

另一个解决方案是使用implicits,将数据库连接定义为函数的隐式参数:

def createUser(user: User)(implicit dbName: String): Option[User]= 
  DB.withConnection(dbName) { ... }

您仍需要在所有通话中向上传播参数,但您可以隐藏它:     def importUsers(csvFile:File)(隐式dbName:String):Seq [User] = {conn =>         ...         User.createUser(U)         ...     }

当你从顶部打电话时:

implicit dbName = "test"
importUsers(...)

这是在scala中构建的,因此它很容易设置,并且不需要很多支持它的样板。就个人而言,我认为暗示使代码不清楚,我更喜欢本演示文稿中提出的解决方案Dead-Simple Dependency Injection

它的要点是,使createUser和依赖于数据库连接的所有其他方法根据连接返回一个函数,而不仅仅是结果。以下是它如何与您的示例一起使用。

1-您创建一个配置连接的连接特征。一个简单的形式是:

trait ConnectionConfig {
   def dbName: String
}

2-你的方法取决于该配置返回一个函数:

def createUser(user: User): ConnectionConfig => Option[User] = { conn =>

    DB.withConnection(conn.dbName) { ... }

}

3-当你在另一个方法中使用createUser时,该方法也依赖于连接,所以你通过返回ConnectionConfig对函数返回类型的依赖来标记它,例如:

def importUsers(csvFile: File): ConnectionConfig => Seq[User] = { conn =>
    ...
    User.createUser(u)(conn)
    ...
}

这是一个很好的习惯,因为在您的代码中将清楚哪些方法依赖于与数据库的连接,并且您可以轻松地交换连接。因此,在您的主应用程序中,您将创建真正的连接:

class RealConncetionConfig extends ConnectionConfig {
   val dbName = "xxx_test"
}

但是在您的测试文件中,您创建了一个测试数据库配置:

class DBEvolutionsTest extends Specification {

  class TestDBConfig extends ConnectionConfig {
      val dbName = "h2"
  }

  val testDB = new TestDBConfig()

  "The Database" should {
    "persist data properly" in {
      running(FakeApplication(additionalConfiguration = inMemoryDatabase())) {

        User.create(User(Id[Long](1L), "jakob",
          "aa",
          "aaa",
          true,
          DateTime.now(),
          DateTime.now(),
          DateTime.now(),
          true))(testDB) 

        val newUser = User.findBy(Id[Long](1L))

        newUser.get.firstName must beEqualTo("jakob")
      }
      }
    }

}

这是它的要点。查看我提到的演示文稿和幻灯片,有一种很好的方法来抽象所有这些,这样你就可以放弃使这段代码变得丑陋的(conn)参数。

作为旁注,如果我是你,我甚至会抽象出DB的类型。因此,不要在User模型对象中使用SQL,而是将其放在单独的实现中,这样您就可以轻松切换数据库类型(使用mongodb,dynamo ...)。 它就是这样的,从前面的代码扩展而来:

trait ConnectionConfig {
   def createUser(user: User): Option[User]
}

并在用户模型对象中:

def createUser(user: User): ConnectionConfig => Option[User] = { conn =>
    conn.createUser(user)
}

这样,在根据用户模型测试部分代码时,您可以创建一个模拟数据库,其中createUser始终工作并返回预期结果(或总是失败...),甚至不使用内存数据库(您仍然需要测试真正的SQL连接,但您可以测试应用程序的其他部分):

trait ConnectionConfig {
    def createUser(user: User): Option[User] = Some(user)
}

答案 1 :(得分:0)

inMemoryDatabase方法的定义如下:

def inMemoryDatabase(
  name: String = "default", 
  options: Map[String, String] = Map.empty[String, String]): Map[String, String]

我的猜测是你应该将xxx_test作为name参数传递。

答案 2 :(得分:0)

您必须为内存数据库定义默认名称(xxx_test)以外的名称。我认为以下代码段应该有用。

FakeApplication(additionalConfiguration = inMemoryDatabase("h2"))

另请参阅:https://stackoverflow.com/a/11029324/2153190