3

私たちは Scala 2.10.2 を使用しており、DAO にはSlick 1.0.1 を使用しています。私たちは ScalaMock で DAO をモックしようとしています。私は、モックされた DAO注入する良い方法を見つけようとしています。私は Java を数年間使用してきましたが、2 週間前に 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]

Users やその他のシングルトン 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
}

すべての参照を からUsersに変更していますがUsers()、これにより余計な混乱が生じることはありません。ただし、 vars の使用はDAORepo悪臭がするので、これを改善するための提案があるかどうか疑問に思っています。

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]
}

しかし、新しい特性をどのように注入するかはまだわかりません。私は次のようなことができます

class DAORepoImpl extends DAORepo { }

object DAOWrapper {
    var repo : DAORepo = new DAORepoImpl
}

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

object DAORepoこれは の 20 個の var を の 1 つの var に置き換えますが、varobject DAOWrapperなしでこれを行うためのクリーンな方法があるはずです。

4

1 に答える 1

5

私はあなたのすべてのクラスとあなたの特徴を理解していません。

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 を挿入するには、cake パターンと self 型参照を使用します。


Spring の人々がケーキのパターンを理解するのに役立つ記事を InfoQ FR で近日中に公開する予定です。この記事のコード サンプルは次のとおりです。

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)
    }
  }
}

これは Spring 宣言とほぼ同じであることに注意してください。

<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"/>

問題に戻るには、ケーキ パターンを使用して、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(...)

構築されたアプリケーションは、アプリケーション コンテキストのようなものです。

于 2013-08-17T10:31:05.667 に答える