2013-08-16 39 views
3

我们使用的是Scala 2.10.2,我们的DAO使用的是Slick 1.0.1。我们试图用ScalaMock来嘲笑DAO,并且我试图找出注入模拟DAO的好方法。我已经使用Java好几年了,但是我刚刚在两周前开始使用Scala。我该如何在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") 
    } 
} 

所有的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 
} 

我们都在我们的参考的改变从UsersUsers(),不加杂波过量。然而,在DAORepo中使用变种臭味很差,我想知道是否有人有建议来改善这一点。

我读过Real-World Scala: Dependency Injection (DI)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] 
} 

,但我还是不明白,我怎么会注入新的特质。我可以做类似

class DAORepoImpl extends DAORepo { } 

object DAOWrapper { 
    var repo : DAORepo = new DAORepoImpl 
} 

def test = { 
    DAOWrapper.repo = new DAORepoImpl with UsersTestRepo 
} 

它取代object DAORepo二十几瓦尔在object DAOWrapper一个变种,但它好像有应该是一个干净的方式来做到这一点没有任何增值经销商。

+0

感谢赏金和好运与蛋糕的图案:)它并不难,但有关于这个问题缺乏良好的资源,它解释前的Java用户 –

+0

Sebastien Lorber谢谢,您的Spring示例非常有帮助 –

回答

3

我不明白你所有的课程和你的特质。

trait UsersRepo { 
    val usersDAO : Users.type = Users 
} 

trait AnotherRepo { 
    val anotherDAO : Another.type = Another 
} 

trait DAORepo extends UsersRepo with AnotherRepo 

然后你就可以实例化一个真正的RealDAORepo

object RealDAORepo extends DAORepo { } 

还是一个嘲笑一个

object MockedDAORepo extends DAORepo { 
    override val usersDAO : Users.type = mock[Users] 
    override val anotherDAO : Another.type = mock[Another] 
} 

然后注入DAORepo在你的应用程序,您可以用蛋糕图案和自类型引用来做到这一点。


我很快就会发布一篇关于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) 
    } 
    } 
} 

注意这几乎是一样的春天声明:

<bean name="userTweetService" class="service.impl.DefaultUserTweetService"> 
    <property name="userRepository" ref="userRepository"/> 
    <property name="tweetRepository" ref="tweetRepository"/> 
</bean> 

当你这样做:

trait MyApplicationMixin 
    extends DefaultUserTweetServiceComponent 
    with InMemoryUserRepositoryComponent 
    with InMemoryTweetRepositoryComponent 

这几乎一样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" /> 

然后你就可以使用该应用具有:

val app = new MyApplicationMixin { } 

或者

val app = new MyApplicationMixin { 
    override val tweetRepository = mock[TweetRepository] 
} 

后者将是相同的一个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"/> 

所以要回到您的问题,您可以使用蛋糕模式并在您的应用程序中创建服务组件n,这取决于你的DAORepo特性。

然后你就可以这样做:

trait MyApplicationMixin 
     extends DefaultUserServiceComponent 
     with AnotherServiceComponent 
     with DAORepo 

然后:

val app = new MyApplicationMixin { } 

或者

val app = new MyApplicationMixin { 
    override val usersDAO : Users.type = mock[Users] 
    override val anotherDAO : Another.type = mock[Another] 
} 

一旦你的应用程序建立起来,你可以用它这样的:

app.userService.createUser(...) 

构建的应用程序是非常喜欢的应用程序上下文

相关问题