How to use mocks with the Cake Pattern
Asked Answered
T

2

8

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?

Triphibious answered 13/9, 2013 at 16:39 Comment(0)
W
13

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.

Widdershins answered 14/9, 2013 at 9:3 Comment(1)
+1 Thanks for this. My cake recipe has improved considerably with this model.Inkling
T
0

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

Triphibious answered 13/9, 2013 at 17:2 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.