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?
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.