Как использовать макеты с шаблоном торта

У меня есть следующий класс:

class LinkUserService() {

  //** cake pattern **
  oauthProvider: OAuthProvider =>
  //******************

  def isUserLinked(userId: String, service: String) = {
    val cred = oauthProvider.loadCredential(userId)
    cred != null

  }

  def linkUserAccount(userId: String, service: String): (String, Option[String]) = {
    if (isUserLinked(userId, service)) {
      ("SERVICE_LINKED", None)
    } else {
      val authUrl = oauthProvider.newAuthorizationUrl
      ("SERVICE_NOT_LINKED", Some(authUrl))
    }
  }

  def setLinkAuthToken(userId: String, service:String, token:String):String = {
    oauthProvider.createAndStoreCredential(userId, token)
  }

}

Обычно я бы использовал этот класс в производстве следующим образом:

val linkService = LinkUserService with GoogleOAuthProvider

Когда дело доходит до тестирования, я хочу заменить oauthProvider макетом, который был обучен моим модульным тестом отвечать так: oauthProvider.loadCredential("nobody") returns null. Это возможно? Если да, то как мне настроить модульный тест для этого?


person ThaDon    schedule 13.09.2013    source источник


Ответы (2)


У вас есть эта проблема, потому что вы не используете шаблон торта в полной мере. Если вы напишете что-то вроде

trait LinkUserServiceComponent {
    this: OAuthProviderComponent =>

    val linkUserService = new LinkUserService

    class LinkUserService {
        // use oauthProvider explicitly
        ...
    }
}

trait GoogleOAuthProviderComponent {
    val oauthProvider = new GoogleOAuthProvider

    class GoogleOAuthProvider {
        ...
    }
}

И затем вы используете такой макет:

val combinedComponent = new LinkUserServiceComponent with OAuthProviderComponent {
    override val oauthProvider = mock(...)
}

Тогда ваша проблема исчезнет. Если вы также создадите общие черты интерфейса, подобные этому (и заставите другие компоненты зависеть от интерфейса, а не от реализации):

trait OAuthProviderComponent {
    def oauthProvider: OAuthProvider

    trait OAuthProvider {
        // Interface declaration
    }
}

тогда у вас также будет общий повторно используемый и тестируемый код.

Это очень похоже на ваше предложение, и это действительно суть рисунка торта.

person Vladimir Matveev    schedule 14.09.2013
comment
+1 Спасибо за это. Мой рецепт торта значительно улучшился с этой моделью. - person maasg; 31.01.2014

Единственное решение, которое мне удалось придумать, - это своего рода фиктивная черта делегата, как показано:

  trait MockOAuthProvider extends OAuthProvider {
    val mockProvider = mock[OAuthProvider]

    def loadCredential(userId: String) = mockProvider.loadCredential(userId)

    def newAuthorizationUrl() = mockProvider.newAuthorizationUrl

    def createAndStoreCredential(userId: String, authToken: String) = mockProvider.createAndStoreCredential(userId, authToken)

  }

Я размещаю это в верхней части моей спецификации, а затем, когда я объявляю свой LinkUserService, я добавляю эту черту Mock следующим образом:

  val linkUserService = new LinkUserService() with MockOAuthProvider
  val mockOAuth = linkUserService.mockProvider

Наконец, в моих модульных тестах я делаю такие вещи, как:

mockOAuth.loadCredential("nobody") returns null

Это работает, но я мог бы видеть, что быть PITA, если бы моя черта была больше

person ThaDon    schedule 13.09.2013