Search code examples
unit-testingscalamockitocake-pattern

How to use mocks with the Cake Pattern


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?


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.