I want to test a controller action using Action composition.
Here's an example of the composed action and its test code.
The Secured trait:
trait Secured {
def username(request: RequestHeader) = request.session.get(Security.username)
def onUnauthorized(request: RequestHeader) = Results.Redirect(routes.Auth.login)
def withAuth(f: => String => Request[AnyContent] => Result) = {
Security.Authenticated(username, onUnauthorized) { user =>
Action(request => f(user)(request))
}
}
The controller:
MyController extends Contrller with Secured {
def simple = Action { Ok("ok") }
def simpleWithauth = withAuth { implicit username => implicit request=> Ok("ok") }
}
The test code:
// This work fine
val result1 = controller.simple()(FakeRequest())
// This wont compile
val result2 = controller.simpleWithAuth()(FakeRequest())
The latter would require a Request[Action[AnyContent], AnyContent]
but FakeRequest returns a Request[AnyContent]
Any pointers on how to create a fake request of the appropriate type?
This is what I had to do to test secured action
def wrappedActionResult[A](wrapped: Action[(Action[A], A)], request: Request[A]): Result = wrapped.parser(request).run.await.get match {
case Left(errorResult) => errorResult
case Right((innerAction, _)) => innerAction(request)
}
and the test
running(app) {
val result = wrappedActionResult(FakeController().securedAction, invalidRequest)
status(result) must_== UNAUTHORIZED
contentAsString(result) must_== "must be authenticated"
}
That doesn't work with Play 2.1 though, types have been changed...
UPDATE:
in Play 2.1 it's even easier
I've added some sugar
implicit class ActionExecutor(action: EssentialAction) {
def process[A](request: Request[A]): Result = concurrent.Await.result(action(request).run, Duration(1, "sec"))
}
and the test now looks like this
running(app) {
val result = FakeController().securedAction process invalidRequest
status(result) must_== UNAUTHORIZED
contentAsString(result) must_== "must be authenticated"
}
UPDATE: here is a related blog post i wrote sometime ago
http://www.daodecode.com/blog/2013/03/08/testing-security-dot-authenticated-in-play-2-dot-0-and-2-dot-1/
Have you tried using the routeAndCall approach?
val Some(result) = routeAndCall(FakeRequest(POST, "/someRoute"))
You can add whatever parameters you need in your request with the asFormUrlEncodedBody method and/or adding things to the session with the withSession method.
Related
I have a route in akka-http app which is integrated with a 3rd party service via Http().cachedHostConnectionPoolHttps. I want to test it in a right way. But not sure how it should be :(
Here is how this route looks like:
val routes: Route = pathPrefix("access-tokens") {
pathPrefix(Segment) { userId =>
parameters('refreshToken) { refreshToken =>
onSuccess(accessTokenActor ? GetAccessToken(userId, refreshToken)) {
case token: AccessToken => complete(ok(token.toJson))
case AccessTokenError => complete(internalServerError("There was problems while retriving the access token"))
}
}
}
}
Behind this route hides accessTokenActor where all logic happens, here it is:
class AccessTokenActor extends Actor with ActorLogging with APIConfig {
implicit val actorSystem = context.system
import context.dispatcher
implicit val materializer = ActorMaterializer()
import AccessTokenActor._
val connectionFlow = Http().cachedHostConnectionPoolHttps[String]("www.service.token.provider.com")
override def receive: Receive = {
case get: GetAccessToken => {
val senderActor = sender()
Source.fromFuture(Future.successful(
HttpRequest(
HttpMethods.GET,
"/oauth2/token",
Nil,
FormData(Map(
"clientId" -> youtubeClientId,"clientSecret" -> youtubeSecret,"refreshToken" -> get.refreshToken))
.toEntity(HttpCharsets.`UTF-8`)) -> get.channelId
)
)
.via(connectionFlow)
.map {
case (Success(resp), id) => resp.status match {
case StatusCodes.OK => Unmarshal(resp.entity).to[AccessTokenModel]
.map(senderActor ! AccessToken(_.access_token))
case _ => senderActor ! AccessTokenError
}
case _ => senderActor ! AccessTokenError
}
}.runWith(Sink.head)
case _ => log.info("Unknown message")
}
}
So the question is how it's better to test this route, keeping in mind that the actor with the stream also exist under its hood.
Composition
One difficulty with testing your route logic, as currently organized, is that it is hard to isolate functionality. It is impossible to test your Route logic without an Actor, and it is hard to test your Actor querying without a Route.
I think you would be better served with function composition, that way you can isolate what it is you're trying to test.
First abstract away the Actor querying (ask):
sealed trait TokenResponse
case class AccessToken() extends TokenResponse {...}
case object AccessTokenError extends TokenResponse
val queryActorForToken : (ActorRef) => (GetAccessToken) => Future[TokenResponse] =
(ref) => (getAccessToken) => (ref ? getAccessToken).mapTo[TokenResponse]
Now convert your routes value into a higher-order method which takes in the query function as a parameter:
val actorRef : ActorRef = ??? //not shown in question
type TokenQuery = GetAccessToken => Future[TokenResponse]
val actorTokenQuery : TokenQuery = queryActorForToken(actorRef)
val errorMsg = "There was problems while retriving the access token"
def createRoute(getToken : TokenQuery = actorTokenQuery) : Route =
pathPrefix("access-tokens") {
pathPrefix(Segment) { userId =>
parameters('refreshToken) { refreshToken =>
onSuccess(getToken(GetAccessToken(userId, refreshToken))) {
case token: AccessToken => complete(ok(token.toJson))
case AccessTokenError => complete(internalServerError(errorMsg))
}
}
}
}
//original routes
val routes = createRoute()
Testing
Now you can test queryActorForToken without needing a Route and you can test the createRoute method without needing an actor!
You can test createRoute with an injected function that always returns a pre-defined token:
val testToken : AccessToken = ???
val alwaysSuccceedsRoute = createRoute(_ => Success(testToken))
Get("/access-tokens/fooUser?refreshToken=bar" ~> alwaysSucceedsRoute ~> check {
status shouldEqual StatusCodes.Ok
responseAs[String] shouldEqual testToken.toJson
}
Or, you can test createRoute with an injected function that never returns a token:
val alwaysFailsRoute = createRoute(_ => Success(AccessTokenError))
Get("/access-tokens/fooUser?refreshToken=bar" ~> alwaysFailsRoute ~> check {
status shouldEqual StatusCodes.InternalServerError
responseAs[String] shouldEqual errorMsg
}
So, I am starting learning groovy & grails. I am trying to write unit tests for my controller like this:
void testSave() {
params.productName = 'ProdName'
params.productBarCode = '123'
params.productStore = 'ProdStore'
def response = controller.save()
assert response.productInstance.productName == 'ProdName'
}
and this is the controller action
def save() {
def productInstance = new Product(params)
if (!productInstance.save(flush: true)) {
render(view: "create", model: [productInstance: productInstance])
return
}
flash.message = message(code: 'default.created.message', args: [message(code: 'product.label', default: 'Product'), productInstance.id])
redirect(action: "show", id: productInstance.id)
}
and this is the exception it throws when 'test-app'
groovy.lang.MissingMethodException: No signature of method: xxx.Product.save() is applicable for argument types: () values: []
Possible solutions: save(), save(boolean), save(java.util.Map), wait(), any(), wait(long)
at xxx.ProductController.save(ProductController.groovy:59)
at xxx.ProductControllerTests.testSave(ProductControllerTests.groovy:35)
I am sorry if this question is too naive. Please help
Thanks
Until the domain instance is mocked in the test class, it won't be able to recognize dynamic methods like save() on the domain class.
Use #Mock(Product) at class level in the test class.
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
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.
I have a simple spray client :
val pipeline = sendReceive ~> unmarshal[GoogleApiResult[Elevation]]
val responseFuture = pipeline {Get("http://maps.googleapis.com/maps/api/elevation/jsonlocations=27.988056,86.925278&sensor=false") }
responseFuture onComplete {
case Success(GoogleApiResult(_, Elevation(_, elevation) :: _)) =>
log.info("The elevation of Mt. Everest is: {} m", elevation)
shutdown()
case Failure(error) =>
log.error(error, "Couldn't get elevation")
shutdown()
}
Full code can be found here.
I want to mock the response of the server to test the logic in the Success and Failure cases. The only relevant information i found was here but I haven't been able to use the cake pattern to mock the sendReceive method.
Any suggestion or example would be greatly appreciated.
Here's an example of one way to mock it using specs2 for the test spec and mockito for the mocking. First, the Main object refactored into a class setup for mocking:
class ElevationClient{
// we need an ActorSystem to host our application in
implicit val system = ActorSystem("simple-spray-client")
import system.dispatcher // execution context for futures below
val log = Logging(system, getClass)
log.info("Requesting the elevation of Mt. Everest from Googles Elevation API...")
import ElevationJsonProtocol._
import SprayJsonSupport._
def sendAndReceive = sendReceive
def elavation = {
val pipeline = sendAndReceive ~> unmarshal[GoogleApiResult[Elevation]]
pipeline {
Get("http://maps.googleapis.com/maps/api/elevation/json?locations=27.988056,86.925278&sensor=false")
}
}
def shutdown(): Unit = {
IO(Http).ask(Http.CloseAll)(1.second).await
system.shutdown()
}
}
Then, the test spec:
class ElevationClientSpec extends Specification with Mockito{
val mockResponse = mock[HttpResponse]
val mockStatus = mock[StatusCode]
mockResponse.status returns mockStatus
mockStatus.isSuccess returns true
val json = """
{
"results" : [
{
"elevation" : 8815.71582031250,
"location" : {
"lat" : 27.9880560,
"lng" : 86.92527800000001
},
"resolution" : 152.7032318115234
}
],
"status" : "OK"
}
"""
val body = HttpEntity(ContentType.`application/json`, json.getBytes())
mockResponse.entity returns body
val client = new ElevationClient{
override def sendAndReceive = {
(req:HttpRequest) => Promise.successful(mockResponse).future
}
}
"A request to get an elevation" should{
"return an elevation result" in {
val fut = client.elavation
val el = Await.result(fut, Duration(2, TimeUnit.SECONDS))
val expected = GoogleApiResult("OK",List(Elevation(Location(27.988056,86.925278),8815.7158203125)))
el mustEqual expected
}
}
}
So my approach here was to first define an overridable function in the ElevationClient called sendAndReceive that just delegates to the spray sendReceive function. Then, in the test spec, I override that sendAndReceive function to return a function that returns a completed Future wrapping a mock HttpResponse. This is one approach for doing what you want to do. I hope this helps.
There's no need to introduce mocking in this case, as you can simply build a HttpResponse much more easily using the existing API:
val mockResponse = HttpResponse(StatusCodes.OK, HttpEntity(ContentTypes.`application/json`, json.getBytes))
(Sorry for posting this as another answer, but don't have enough karma to comment)
I am trying to figure out how to write my Test cases for a service I am going to write.
The service will use HTTPBuilder to request a response from some URL. The HTTPBuilder request only needs to check the response for a success or failure. The service implementation will be be something as simple as:
boolean isOk() {
httpBuilder.request(GET) {
response.success = { return true }
response.failure = { return false }
}
}
So, I want to be able to mock the HTTPBuilder so that I can set the response to be either success/failure in my test so I can assert that my service's isOk method returns True when the response is a success and False, when the response is a failure.
Can any one help with how I can mock the HTTPBuilder request and set the response in a GroovyTestCase?
Here's a minimal example of a mock HttpBuilder that will handle your test case:
class MockHttpBuilder {
def result
def requestDelegate = [response: [:]]
def request(Method method, Closure body) {
body.delegate = requestDelegate
body.call()
if (result)
requestDelegate.response.success()
else
requestDelegate.response.failure()
}
}
If the result field is true, it'll invoke the success closure, otherwise failure.
EDIT: Here's an example using MockFor instead of a mock class:
import groovy.mock.interceptor.MockFor
def requestDelegate = [response: [:]]
def mock = new MockFor(HttpBuilder)
mock.demand.request { Method method, Closure body ->
body.delegate = requestDelegate
body.call()
requestDelegate.response.success() // or failure depending on what's being tested
}
mock.use {
assert isOk() == true
}