如何使用蛋糕模式的模拟

时间:2013-09-13 16:39:33

标签: unit-testing scala mockito cake-pattern

我有以下课程:

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。这可能吗?如果是这样,我将如何设置我的单元测试呢?

2 个答案:

答案 0 :(得分:12)

您遇到此问题,因为您没有完全使用蛋糕模式。如果你写了像

这样的东西
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
    }
}

然后你也会有通用的可重用和可测试的代码。

这与你的建议非常相似,它确实是蛋糕模式的本质。

答案 1 :(得分:0)

我能够提出的唯一解决办法就是证明了一种代表模拟特征:

  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