Zim*_*oot 3 dependency-injection scala mocking
我们使用 Scala 2.10.2,我们使用Slick 1.0.1 作为我们的 DAO。我们正在尝试使用ScalaMock模拟 DAO ,我正在尝试找出一种注入模拟 DAO的好方法。我已经使用 Java 好几年了,但两周前我才开始使用 Scala。
现在我们的代码看起来像(忽略任何语法错误,我已经压缩了代码,但没有确保它仍然满足类型系统)
abstract class RichTable[T](name: String)
extends slick.driver.MySQLDriver.simple.Table[T](name) {
type ItemType = T
def id = column[Int]("id", O.PrimaryKey, O.AutoInc)
...
}
object Users extends RichTable[User]("users") {
def crypted_password = column[String]("crypted_password")
...
}
case class User(id: Option[Int] = None, crypted_password: String) {
def updatePassword(...) = {
Users.where(_.id === id).map{e => e.crypted_password}.update("asdf")
}
}
Run Code Online (Sandbox Code Playgroud)
所有的 DAO 都是继承自的单例对象 RichTable[T]
我们希望能够模拟用户和其他单例 DAO 对象——现在我们所有的单元测试都在访问数据库。然而,我们遇到的问题是如何注入模拟单例对象。到目前为止,我们提出的解决方案是:
object DAORepo {
var usersDAO : Users.type = Users
var anotherDAO : Another.type = Another
...
}
object Users extends RichTable[User]("users") {
def apply() : Users.type = DAORepos.usersDAO
}
def updatePassword(...) = {
Users().where(_.id === id).map{e => e.crypted_password}.update("asdf")
}
def test = {
val mockUsers = mock[Users]
DAORepo.usersDAO = mockUsers
// run test using mock repo
}
Run Code Online (Sandbox Code Playgroud)
我们将所有引用从 更改Users为Users(),这不会增加过多的混乱。但是,在DAORepo气味中使用 vars很糟糕,我想知道是否有人建议改进这一点。
我已经阅读了Real-World Scala: Dependency Injection (DI) and Component Based Dependency Injection in Scala - 我想我了解如何使用特征来组成 DAORepo,例如
trait UsersRepo {
val usersDAO : Users.type = Users
}
trait DAORepo extends UsersRepo with AnotherRepo { }
trait UsersTestRepo {
val usersDAO : Users.type = mock[Users]
}
Run Code Online (Sandbox Code Playgroud)
但我仍然不明白我将如何注入新特性。我可以做类似的事情
class DAORepoImpl extends DAORepo { }
object DAOWrapper {
var repo : DAORepo = new DAORepoImpl
}
def test = {
DAOWrapper.repo = new DAORepoImpl with UsersTestRepo
}
Run Code Online (Sandbox Code Playgroud)
它object DAORepo用一个 var in替换了两个 vars in object DAOWrapper,但似乎应该有一种干净的方法来做到这一点,而没有任何 vars。
我不明白你所有的课程和你的特点。
trait UsersRepo {
val usersDAO : Users.type = Users
}
trait AnotherRepo {
val anotherDAO : Another.type = Another
}
trait DAORepo extends UsersRepo with AnotherRepo
Run Code Online (Sandbox Code Playgroud)
然后你可以实例化一个真正的 RealDAORepo
object RealDAORepo extends DAORepo { }
Run Code Online (Sandbox Code Playgroud)
或者被嘲笑的
object MockedDAORepo extends DAORepo {
override val usersDAO : Users.type = mock[Users]
override val anotherDAO : Another.type = mock[Another]
}
Run Code Online (Sandbox Code Playgroud)
然后在你的应用程序中注入 DAORepo,你可以使用 cake 模式和 self 类型引用来做到这一点。
我很快会在 InfoQ FR 上发表一篇文章,帮助 Spring 的人们理解蛋糕模式。这是本文的代码示例:
trait UserTweetServiceComponent {
val userTweetService: UserTweetService
}
trait UserTweetService {
def createUser(user: User): User
def createTweet(tweet: Tweet): Tweet
def getUser(id: String): User
def getTweet(id: String): Tweet
def getUserAndTweets(id: String): (User,List[Tweet])
}
trait DefaultUserTweetServiceComponent extends UserTweetServiceComponent {
// Declare dependencies of the service here
self: UserRepositoryComponent
with TweetRepositoryComponent =>
override val userTweetService: UserTweetService = new DefaultUserTweetService
class DefaultUserTweetService extends UserTweetService {
override def createUser(user: User): User = userRepository.createUser(user)
override def createTweet(tweet: Tweet): Tweet = tweetRepository.createTweet(tweet)
override def getUser(id: String): User = userRepository.getUser(id)
override def getTweet(id: String): Tweet = tweetRepository.getTweet(id)
override def getUserAndTweets(id: String): (User,List[Tweet]) = {
val user = userRepository.getUser(id)
val tweets = tweetRepository.getAllByUser(user)
(user,tweets)
}
}
}
Run Code Online (Sandbox Code Playgroud)
请注意,这与 Spring 声明几乎相同:
<bean name="userTweetService" class="service.impl.DefaultUserTweetService">
<property name="userRepository" ref="userRepository"/>
<property name="tweetRepository" ref="tweetRepository"/>
</bean>
Run Code Online (Sandbox Code Playgroud)
当你这样做时:
trait MyApplicationMixin
extends DefaultUserTweetServiceComponent
with InMemoryUserRepositoryComponent
with InMemoryTweetRepositoryComponent
Run Code Online (Sandbox Code Playgroud)
它与 Spring 声明几乎相同(但您获得了类型安全的应用程序上下文):
<import resource="classpath*:/META-INF/application-context-default-tweet-services.xml" />
<import resource="classpath*:/META-INF/application-context-inmemory-tweet-repository.xml" />
<import resource="classpath*:/META-INF/application-context-inmemory-user-repository.xml" />
Run Code Online (Sandbox Code Playgroud)
然后您可以使用该应用程序:
val app = new MyApplicationMixin { }
Run Code Online (Sandbox Code Playgroud)
或者
val app = new MyApplicationMixin {
override val tweetRepository = mock[TweetRepository]
}
Run Code Online (Sandbox Code Playgroud)
后者将与 Spring bean 覆盖相同:
<import resource="classpath*:/META-INF/application-context-default-tweet-services.xml" />
<import resource="classpath*:/META-INF/application-context-inmemory-tweet-repository.xml" />
<import resource="classpath*:/META-INF/application-context-inmemory-user-repository.xml" />
<!--
This bean will override the one defined in application-context-inmemory-tweet-repository.xml
But notice that Spring isn't really helpful to declare the behavior of the mock, which is much
easier with the cake pattern since you directly write code
-->
<bean id="tweetRepository" class="repository.impl.MockedTweetRepository"/>
Run Code Online (Sandbox Code Playgroud)
因此,回到您的问题,您可以使用蛋糕模式并在您的应用程序中创建服务组件,这取决于您的 DAORepo 特征。
然后你可以这样做:
trait MyApplicationMixin
extends DefaultUserServiceComponent
with AnotherServiceComponent
with DAORepo
Run Code Online (Sandbox Code Playgroud)
进而:
val app = new MyApplicationMixin { }
Run Code Online (Sandbox Code Playgroud)
或者
val app = new MyApplicationMixin {
override val usersDAO : Users.type = mock[Users]
override val anotherDAO : Another.type = mock[Another]
}
Run Code Online (Sandbox Code Playgroud)
构建应用程序后,您可以像这样使用它:
app.userService.createUser(...)
Run Code Online (Sandbox Code Playgroud)
构建的应用程序真的很像一个应用程序上下文
| 归档时间: |
|
| 查看次数: |
3246 次 |
| 最近记录: |