2013-08-16 38 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(...) 

構建的應用程序是非常喜歡的應用程序上下文

相關問題