我正在试图弄清楚如何在我的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}}
你是如何解决这个问题的?
答案 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"))