我正在尝试弄清楚如何在 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=""
在我的用户对象中,我指定了运行应用程序时要使用的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
数据库。该测试将在真实数据库中创建一个用户,而不是内存中的用户,因为我已在 User(DB.withConnection("xxx_test")
) 对象中指定了数据库。
我想有一些聪明的方法可以做到这一点,我不想传递数据库名称
像 User.create(User(...), "xxx_test")
你是如何解决这个问题的?
最佳答案
您可能想了解如何在 scala 中进行依赖项注入(inject)。一个好的解决方案是将数据库从用户模型中抽象出来,然后将其作为依赖项传递。
一个简单的方法是更改配置文件以进行测试。玩让你specify which config file is used on the command line 。 但这不是最实用的。
另一个解决方案是使用隐式,将数据库连接定义为函数的隐式参数:
def createUser(user: User)(implicit dbName: String): Option[User]=
DB.withConnection(dbName) { ... }
您仍然需要在所有调用中向上传播参数,但您可以隐藏它: def importUsers(csvFile: File)(implicit dbName: String): Seq[User] = { conn => ... 用户.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)
参数。
顺便说一句,如果我是你,我什至会抽象出数据库的类型。因此,不要将 SQL 放在 User 模型对象中,而是将其放在单独的实现中,这样您就可以轻松切换数据库类型(使用 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)
}
关于scala - Play2 和 Scala,我应该如何配置集成测试以使用正确的数据库运行,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/15505234/