Question

I am trying to invent some kind of mocking SecureSocial action generators, or SecureSocial itself to be able to unit-test controller methods. I've found some approaches, like Unit-testing methods secured with Securesocial annotation and Testing a Play2 application with SecureSocial using dependency injection but the thing is, that in that questions authors, in fact, don't do unit testing, but integration testing.

My unit tests look like this:

  trait MockDaoProvider extends IDaoProvider {
    def entityDao = entityDaoMock
  }

  val controller = new MyController with MockDaoProvider

  "MyController.list" should {
    "return an OK" in {
      entityDaoMock.list().returns(List())

      val result = controller.list()(FakeRequest())

      status(result) must equalTo(OK)
    }
  }

As one can see, I mocked dependencies to isolate and test the behavior that controller method actually does.

Everything was OK until I used SecuredAction from securesocial for MyController.list method. Now I get an exception, and the test fails. I have no idea how I could mock, stub or override SecuredAction and UserAwareAction objects from securesocial. Still I don't want to convert my tests into route(...) tests. They are intended to test only the controller's behavior.

Have someone encountered the same problem? May be there are any hints how it could be solved?

PS: Play framework 2.2.1, securesocial - 2.1.2

No correct solution

OTHER TIPS

It seem like the author of the code really hasn't emphasized testability, which has forced users to come up with their own novel solutions. This one by user jeantil could be helpful:

class FakeAuthenticatorStore(app:Application) extends AuthenticatorStore(app) {
  var authenticator:Option[Authenticator] = None
  def save(authenticator: Authenticator): Either[Error, Unit] = {
    this.authenticator=Some(authenticator)
    Right()
  }
  def find(id: String): Either[Error, Option[Authenticator]] = {
    Some(authenticator.filter(_.id == id)).toRight(new Error("no such authenticator"))
  }
  def delete(id: String): Either[Error, Unit] = {
    this.authenticator=None
    Right()
  }
}

abstract class WithLoggedUser(val user:User,override val app: FakeApplication = FakeApplication()) extends WithApplication(app) with Mockito{
  lazy val mockUserService=mock[UserService]
  val identity=IdentityUser(Defaults.googleId, user)

  import helpers._
  import TestUsers._
  def cookie=Authenticator.create(identity) match {
    case Right(authenticator) => authenticator.toCookie
  }

  override def around[T: AsResult](t: =>T): execute.Result = super.around {
    mockUserService.find(Defaults.googleId) returns Some(identity)
    UserService.setService(mockUserService)
    t
  }
}

  val excludedPlugins=List(
    ,"service.login.MongoUserService"
    ,"securesocial.core.DefaultAuthenticatorStore"
  )
  val includedPlugins = List(
    "helpers.FakeAuthenticatorStore"
  )

  def minimalApp = FakeApplication(withGlobal =minimalGlobal, withoutPlugins=excludedPlugins,additionalPlugins = includedPlugins)

which then allows testing like this

"create a new user password " in new WithLoggedUser(socialUser,minimalApp) {
  val controller = new TestController
  val req: Request[AnyContent] = FakeRequest().
    withHeaders((HeaderNames.CONTENT_TYPE, "application/x-www-form-urlencoded")).
    withCookies(cookie) // Fake cookie from the WithloggedUser trait

  val requestBody = Enumerator("password=foobarkix".getBytes) andThen Enumerator.eof
  val result = requestBody |>>> controller.create.apply(req)

  val actual: Int= status(result)
  actual must be equalTo 201
}

After some thinking, probing and experimenting I've ended up with an elegant solution. The solution relies on "cake pattern" of dependency injection. Like this:

Code in controller:

trait AbstractSecurity {
  def Secured(action: SecuredRequest[AnyContent] => Result): Action[AnyContent]
}

trait SecureSocialSecurity extends AbstractSecurity with securesocial.core.SecureSocial {
   def Secured(action: SecuredRequest[AnyContent] => Result): Action[AnyContent] = SecuredAction { action }
}

abstract class MyController extends Controller with AbstractSecurity {

  def entityDao: IEntityDao

  def list = Secured { request =>
    Ok(
      JsArray(entityDao.list())
    )
  }
}

object MyController extends MyController with PsqlDaoProvider with SecureSocialSecurity

And test code:

 trait MockedSecurity extends AbstractSecurity {
    val user = Account(NotAssigned, IdentityId("test", "userpass"), "Test", "User",
      "Test user", Some("test@user.com"), AuthenticationMethod("userPassword"))

    def Secured(action: SecuredRequest[AnyContent] => play.api.mvc.Result): Action[AnyContent] = Action { request =>
      action(new SecuredRequest(user, request))
    }
  }


  val controller = new MyController with MockDaoProvider with MockedSecurity

  "IssueController.list" should {
    "return an OK" in {
      entityDaoMock.list().returns(List())

      val result = controller.list()(FakeRequest())

      status(result) must equalTo(OK)
    }
  }

Still there is a drawback - the tests depends on securesocial classes as well... but... is it really a drawback? I don't know how this approach will work in more complex situations, we'll see.

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