Testing a Play2 application with SecureSocial usin

2019-01-24 19:06发布

问题:

Thanks a lot for any guidance!

The SecureSocial plugin works fine when I run it from the browser, but I would like to be able to test the rest of my Play app now.

Quick Intro

SecureSocial's syntax looks like this:

def page = SecuredAction(WithProvider("google")) { ... }

or this:

def page = UserAwareAction { ... }

I've been looking here, which seems to be the only question on Stack Overflow even remotely related to my problem with SecureSocial, but I don't quite fancy rewiring bytecode. There should be a simpler solution to this.

When I'm running tests that access SecureSocial-protected Actions, I get a large error that I guess basically means I'm not passing it a user. (see the bottom of this question)

What I would like to do

Either inject all the functions to return a type Action instead of SecuredAction or UserAwareAction only during testing

Or actually pass in a test user to the call. But how?

What I have

@Singleton
class JsonOps @Inject() () extends Controller with SecureSocial {...}

A Global.scala written as described here and in my test...

val controller = new JsonOps
val result = controller.userAwareActionRequestForSomeJson("")(FakeRequest())

I also have calls like this:

// This is what I would use for production
def extjs = SecuredAction(WithProvider("google")) { implicit request =>
   Ok(views.html.extjs(request.user.firstName))
}
// This is what I would use for testing
def extjs = Action { implicit request =>
  Ok(views.html.extjs("testtesttesting"))
}

Which is why I think this problem might be well-suited for dependency injection? I'm not sure how I would do the class instantiation though, since the Global.scala I'm using is a generic class instantiator. I don't particularly wish to write 9000+ traits for each controller I have either.

The big error

This is line UserOpsSpec.scala line 12 and 13:

12  val controller = new UserOps
13  val result = controller.extjs()(FakeRequest())

and this is the error

[error]     RuntimeException: java.lang.ExceptionInInitializerError (UserOpsSpec.scala:13)
[error] play.api.mvc.ActionBuilder$$anon$1.apply(Action.scala:220)
[error] securesocial.core.SecureSocial$.authenticatorFromRequest(SecureSocial.scala:200)
[error] securesocial.core.SecureSocial$$anonfun$SecuredAction$1.apply(SecureSocial.scala:81)
[error] securesocial.core.SecureSocial$$anonfun$SecuredAction$1.apply(SecureSocial.scala:78)
[error] play.api.mvc.ActionBuilder$$anon$1.apply(Action.scala:215)
[error] play.api.Play$$anonfun$current$1.apply(Play.scala:51)
[error] play.api.Play$$anonfun$current$1.apply(Play.scala:51)
[error] play.api.Play$.current(Play.scala:51)
[error] securesocial.core.Authenticator$.cookieName$lzycompute(Authenticator.scala:188)
[error] securesocial.core.Authenticator$.cookieName(Authenticator.scala:188)
[error] securesocial.core.Authenticator$.<init>(Authenticator.scala:201)
[error] securesocial.core.Authenticator$.<clinit>(Authenticator.scala)
[error] securesocial.core.SecureSocial$.authenticatorFromRequest(SecureSocial.scala:200)
[error] securesocial.core.SecureSocial$$anonfun$SecuredAction$1.apply(SecureSocial.scala:81)
[error] securesocial.core.SecureSocial$$anonfun$SecuredAction$1.apply(SecureSocial.scala:78)
[error] play.api.mvc.ActionBuilder$$anon$1.apply(Action.scala:215)
[error] null
[error] securesocial.core.SecureSocial$.authenticatorFromRequest(SecureSocial.scala:200)
[error] securesocial.core.SecureSocial$$anonfun$SecuredAction$1.apply(SecureSocial.scala:81)
[error] securesocial.core.SecureSocial$$anonfun$SecuredAction$1.apply(SecureSocial.scala:78)
[error] play.api.mvc.ActionBuilder$$anon$1.apply(Action.scala:215)
[error] There is no started application
[error] play.api.Play$$anonfun$current$1.apply(Play.scala:51)
[error] play.api.Play$$anonfun$current$1.apply(Play.scala:51)
[error] play.api.Play$.current(Play.scala:51)
[error] securesocial.core.Authenticator$.cookieName$lzycompute(Authenticator.scala:188)
[error] securesocial.core.Authenticator$.cookieName(Authenticator.scala:188)
[error] securesocial.core.Authenticator$.<init>(Authenticator.scala:201)
[error] securesocial.core.Authenticator$.<clinit>(Authenticator.scala)
[error] securesocial.core.SecureSocial$.authenticatorFromRequest(SecureSocial.scala:200)
[error] securesocial.core.SecureSocial$$anonfun$SecuredAction$1.apply(SecureSocial.scala:81)
[error] securesocial.core.SecureSocial$$anonfun$SecuredAction$1.apply(SecureSocial.scala:78)
[error] play.api.mvc.ActionBuilder$$anon$1.apply(Action.scala:215)
[info]

回答1:

Here's how I solved this problem. I am simulating a login (kind of) by directly adding to the Authenticator that's in memory. That returns a cookie which I include in the fake request. I'm doing this with an implicit conversion for some syntactic sugar. You can easily extend it for your particular case. My application only uses the user/pass provider, but you should be able to extend to use the other plugins.

 object TestUtils {

  @inline implicit def loggedInFakeRequestWrapper[T](x: FakeRequest[T]) = new LoggedInFakeRequest(x)

  final class LoggedInFakeRequest[T](val self: FakeRequest[T]) extends AnyVal {
    def withLoggedInUser(id: Long) = {
      val userToLogInAs:Identity = ??? //get this from your database using whatever you have in Global
      val cookie = Authenticator.create(userToLogInAs) match {
        case Right(authenticator) => authenticator.toCookie
      }
      self.withCookies(cookie)
    }
  }        
}

And the spec:

"render the index page" in {
      val home = route(FakeRequest(GET, "/").withLoggedInUser(1L)).get

      status(home) must equalTo(OK)
      //etc.
    }