Testing
Test Helpers
Silhouette provides some test helpers that can be used to easily test your Silhouette application. These helpers are located in the additional TestKit dependency.
libraryDependencies ++= Seq(
"com.mohiva" %% "play-silhouette-testkit" % "version" % "test"
)
After providing the dependency, the helpers can be used by importing the following package into your test scenarios.
import com.mohiva.play.silhouette.test._
All helpers are test framework agnostic. You can use it with Specs2, ScalaTest or every other testing framework. In our examples we use Specs2 to demonstrate how to test Silhouette applications, because until Play 2.4 specs2 was to be the default framework shipped with Play and we still use specs2 to test Silhouette.
Test Silhouette actions
With the previous mentioned test helpers it's really easy to test your Silhouette actions.
As first lets look how a typical controller instance could look like:
class UserController(silhouette: Silhouette[MyEnv]) extends Controller {
/**
* Gets a user.
*/
def user = silhouette.SecuredAction { implicit request =>
Ok(Json.toJson(request.identity))
}
/**
* Checks if a user is authenticated.
*/
def isAuthenticated = silhouette.UserAwareAction { implicit request =>
request.identity match {
case Some(identity) => Ok
case None => Unauthorized
}
}
}
If you would like to test this controller, you must provide an environment that can handle your Identity
and Authenticator
implementation. For this case Silhouette provides a FakeEnvironment
which automatically sets up all components needed to test your specific actions.
FakeEnvironment
The fake environment does all the annoying steps for you to create and instantiate all dependencies that you need for your test. You must only specify one or more LoginInfo -> Identity
pairs that should be returned by calling request.identity
in your action and the authenticator instance that tracks this user.
val identity = User(LoginInfo("facebook", "[email protected]"))
implicit val env = FakeEnvironment[MyEnv](Seq(identity.loginInfo -> identity))
Under the hood, the environment instantiates a FakeIdentityService
which stores your given identities and returns it if needed. It instantiates also the appropriate AuthenticatorService
based on your defined Authenticator
type. All Authenticator
services are real service instances set up with their default values and dependencies.
FakeRequest
Let us summarize briefly how an Action
in Play works. An action is basically an anonymous function that handles a Request
and returns a Result
. With this in mind we must now create a request that we can pass to our action. Play ships with a test helper called FakeRequest
which does exactly what we want. But this helper cannot embed an Authenticator
into the created fake request. Therefore we have extend Play's fake request helper with two additional methods.
The first method accepts an Authenticator
instance which is then embedded into the request.
val identity = User(LoginInfo("facebook", "[email protected]"))
implicit val env = FakeEnvironment[MyEnv](Seq(identity.loginInfo -> identity))
val authenticator = new CookieAuthenticator("test", identity.loginInfo, ...)
val request = FakeRequest().withAuthenticator(authenticator)
The second method accepts a LoginInfo
instance for which an authenticator will be created and embedded into the request.
val identity = User(LoginInfo("facebook", "[email protected]"))
implicit val env = FakeEnvironment[MyEnv](Seq(identity.loginInfo -> identity))
val request = FakeRequest().withAuthenticator(identity.loginInfo)
Note
To embed an authenticator into a request you need an implicit environment in scope.
Tying the Pieces Together
So far, we've learned how to setup a test environment and how to create a request which contains an embedded authenticator. Now we combine these techniques and create a complete controller test.
Simulate a missing authenticator
To simulate that an authenticator couldn't be found for a request, you must only submit a request without an authenticator.
class UserSpec extends PlaySpecification {
"The `user` method" should {
"return status 401 if no authenticator was found" in new WithApplication {
val identity = User(LoginInfo("facebook", "[email protected]"))
val env = FakeEnvironment[MyEnv](Seq(identity.loginInfo -> identity))
val request = FakeRequest()
val controller = app.injector.instanceOf[UserController]
val result = controller.user(request)
status(result) must equalTo(UNAUTHORIZED)
}
}
"The `isAuthenticated` method" should {
"return status 401 if no authenticator was found" in new WithApplication {
val identity = User(LoginInfo("facebook", "[email protected]"))
val env = FakeEnvironment[MyEnv](Seq(identity.loginInfo -> identity))
val request = FakeRequest()
val controller = app.injector.instanceOf[UserController]
val result = controller.isAuthenticated(request)
status(result) must equalTo(UNAUTHORIZED)
}
}
}
Simulate a missing identity
To simulate that an identity couldn't be found for a valid authenticator, you must pass different login information to the user and the authenticator.
class UserSpec extends PlaySpecification {
"The `user` method" should {
"return status 401 if authenticator but no identity was found" in new WithApplication {
val identity = User(LoginInfo("facebook", "[email protected]"))
implicit val env = FakeEnvironment[MyEnv](Seq(identity.loginInfo -> identity))
val request = FakeRequest()
.withAuthenticator(LoginInfo("xing", "[email protected]"))
val controller = app.injector.instanceOf[UserController]
val result = controller.user(request)
status(result) must equalTo(UNAUTHORIZED)
}
}
"The `isAuthenticated` method" should {
"return status 401 if authenticator but no identity was found" in new WithApplication {
val identity = User(LoginInfo("facebook", "[email protected]"))
implicit val env = FakeEnvironment[MyEnv](Seq(identity.loginInfo -> identity))
val request = FakeRequest()
.withAuthenticator(LoginInfo("xing", "[email protected]"))
val controller = app.injector.instanceOf[UserController]
val result = controller.isAuthenticated(request)
status(result) must equalTo(UNAUTHORIZED)
}
}
}
Simulate an authenticated identity
To simulate an authenticated identity we must submit a valid authenticator and the login information of both the authenticator and the identity must be the same.
class UserSpec extends PlaySpecification {
"The `user` method" should {
"return status 200 if authenticator and identity was found" in new WithApplication {
val identity = User(LoginInfo("facebook", "[email protected]"))
implicit val env = FakeEnvironment[MyEnv](Seq(identity.loginInfo -> identity))
val request = FakeRequest().withAuthenticator(identity.loginInfo)
val controller = app.injector.instanceOf[UserController]
val result = controller.user(request)
status(result) must equalTo(OK)
}
}
"The `isAuthenticated` method" should {
"return status 200 if authenticator and identity was found" in new WithApplication {
val identity = User(LoginInfo("facebook", "[email protected]"))
implicit val env = FakeEnvironment[MyEnv](Seq(identity.loginInfo -> identity))
val request = FakeRequest().withAuthenticator(identity.loginInfo)
val controller = app.injector.instanceOf[UserController]
val result = controller.isAuthenticated(request)
status(result) must equalTo(OK)
}
}
}
Test default Play actions
Typically Silhouette authentication code is implemented inside default Play actions. To test such actions you don't need specific helper classes. Here you could use Mockito to mock the Silhouette instances or other related testing tools.
Updated less than a minute ago