Question

I have the following class:

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

}

Typically I'd use this class in production like so:

val linkService = LinkUserService with GoogleOAuthProvider

When it comes to testing, I want to replace the oauthProvider with a mock such that's been trained by my unit test to respond like so: oauthProvider.loadCredential("nobody") returns null. Is this possible? If so, how would I set up my unit test to do so?

Was it helpful?

Solution

You have this problem because you are not using cake pattern to full extent. If you write something like

trait LinkUserServiceComponent {
    this: OAuthProviderComponent =>

    val linkUserService = new LinkUserService

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

trait GoogleOAuthProviderComponent {
    val oauthProvider = new GoogleOAuthProvider

    class GoogleOAuthProvider {
        ...
    }
}

And then you use a mock like this:

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

Then your problem disappears. If you also make generic interface traits like this (and make other components depend on interface, not on implementation):

trait OAuthProviderComponent {
    def oauthProvider: OAuthProvider

    trait OAuthProvider {
        // Interface declaration
    }
}

then you also would have generic reusable and testable code.

This is very similar to your suggestion and it really is the essence of cake pattern.

OTHER TIPS

The only solution I've been able to come up wiht is a sort of delegate mock trait as demonstrated:

  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)

  }

I place that at the top of my Spec, then when I declare my LinkUserService I mix in this Mock trait like so:

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

Finally in my unit tests I do things like:

mockOAuth.loadCredential("nobody") returns null

It works, but I could see that being a PITA if my trait was larger

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top