Sign up ×
Code Review Stack Exchange is a question and answer site for peer programmer code reviews. It's 100% free, no registration required.

I am writing a small oauth2 library for Play! 2.1 scala. As I am still learning I got stuck trying to TDD and ended up writing the code first, then refactoring for testability. By testability I mean two things:

  1. make the code testable by me as the author
  2. make it easy for users (since I will be a user) to stub/mock it out.

The first working version of the code looked like (complete file on github):

object GoogleOAuth2 extends OAuth2(GoogleOAuth2Settings) {

    lazy val signIn = s"${settings.signInUrl}?client_id=${settings.clientId}&redirect_uri=${settings.redirectUri}"+

    def requestAccessTockenWS(code: String): Future[Response] =    
        Map("client_id" -> Seq(settings.clientId)
          , "client_secret" -> Seq(settings.clientSecret)
          , "code" -> Seq(code)
          , "redirect_uri" -> Seq(settings.redirectUri)
          , "grant_type" -> Seq("authorization_code")))

The requestAccessTockensWS method depends on the WS API in Play2! This API is exposed as a scala object whith methods which create builders. These builders allow you to compose and call HTTP requests. In a normal setting I might choose not to test requestAccessTockenWS since it is a very small method with almost no logic of its own. However it is a nice "simple" case of dependecy on a scala object for exploring design patterns which enable tests.

Here is the testcase I ended up writing (complete on github):

  "GoogleOAuth2" should {
    object TestSettings extends GoogleOAuth2Settings {
      override lazy val clientId = testConfig("")
      override lazy val clientSecret = testConfig("")
      override lazy val signInUrl = testConfig("")
      override lazy val accessTokenUrl = testConfig("")
      override lazy val userInfoUrl = testConfig("")
      override lazy val redirectUri = testConfig("")
    class MockedGoogleOauth2 extends GoogleOAuth2(TestSettings) {
      override def url = testUrl _

      lazy val requestHolder: WS.WSRequestHolder = mock[WS.WSRequestHolder]

      def testUrl(s: String): WS.WSRequestHolder = {
        requestHolder.url returns s
    "requestAccessTokens" in {
      import concurrent._
      import concurrent.duration._
      import play.api.http.{ Writeable, ContentTypeOf }
      import org.mockito.Matchers.{ eq => mEq }
      import org.mockito.Matchers
      val underTest = new MockedGoogleOauth2()
      val mockWs = underTest.requestHolder
      val fakeResponse = mock[Response]
      val expected = Map("client_id" -> Seq("client_id"), "client_secret" -> Seq("client_secret"), "code" -> Seq("code"), "redirect_uri" -> Seq("http://redirect"), "grant_type" -> Seq("authorization_code"))
      type Params = Map[String, Seq[String]][Params](mEq(expected))(Matchers.any[Writeable[Params]], Matchers.any[ContentTypeOf[Params]]) returns Future.successful(fakeResponse)

      val willBeResponse = underTest.requestAccessTockenWS("code")

      there was one(mockWs).post(mEq(expected))(any, any)
      Await.result(willBeResponse, 5 milli) must beEqualTo(fakeResponse)


and the corresponding refactored code (complete on github):

abstract class GoogleOAuth2[U <: GoogleOAuth2Settings](override val settings: U) extends OAuth2(settings) {

    lazy val signIn = s"${settings.signInUrl}?client_id=${settings.clientId}&redirect_uri=${settings.redirectUri}"+

    def url = WS.url _

    def requestAccessTockenWS(code: String): Future[Response] = {
      val u = url(settings.accessTokenUrl)
      val params = Map("client_id" -> Seq(settings.clientId)
          , "client_secret" -> Seq(settings.clientSecret)
          , "code" -> Seq(code)
          , "redirect_uri" -> Seq(settings.redirectUri)
          , "grant_type" -> Seq("authorization_code"))

  object GoogleOAuth2 extends GoogleOAuth2(GoogleOAuth2Settings)

The code and the tests work, and the client can use the same technique to mock it out. How can I improve on this?

share|improve this question

Your Answer


By posting your answer, you agree to the privacy policy and terms of service.

Browse other questions tagged or ask your own question.