概要
(play2-authの前準備としての記事になります)
stackable-controllerとは、Play2で認証系を担うplay2-authを裏で支えているライブラリです。
(ちなみに、僕は他でどう使えるのかはよくわかってないです。。logging処理を挟みたいならfilterでいけそうですし、DBのトランザクション管理はService層でDIで解決出来そうなので。)
環境
- Play2.5
- stackable-controller 0.6.0
Elementを用意する
まずはStackの対象とするElementを作っていきます。
ここでは、1/2の確率でlong値を生成して返すという処理を書いてみます。
import akka.actor.ActorSystem
import jp.t2v.lab.play2.stackc.{RequestAttributeKey, RequestWithAttributes, StackableController}
import play.api.mvc.{Controller, Result}
import scala.concurrent.Future
import scala.util.Random
trait MyStackLongElement extends StackableController {
self: Controller =>
val actorSystem: ActorSystem
val random: Random
private case object LongKey extends RequestAttributeKey[Long]
override def proceed[A](req: RequestWithAttributes[A])(f: RequestWithAttributes[A] => Future[Result]): Future[Result] = {
implicit val ctx = actorSystem.dispatcher
val long = random.nextLong()
val isFail = random.nextBoolean()
if(isFail) return Future{ BadRequest }
super.proceed(req.set(LongKey, long))(f)
}
def getStackedLong()(implicit req: RequestWithAttributes[_]): Long = req.get(LongKey).get
}
このElementは、proceedの中でreq.set(LongKey, long)
をしています。
なんらかの処理結果をここでsetしているわけですね。
(ちなみに、条件が合わない場合はそのままBadRequestなどのResultを返すこともできます。)
値をsetしたことによって、このElementを実装したcontrollerの中では、getStackedLongを通じてreq.get(LongKey).get
を行い値を取得することが出来るようになります。
Controllerを実装する
import javax.inject.{Inject, Singleton}
import akka.actor.ActorSystem
import play.api.mvc.Controller
import scala.concurrent.{ExecutionContext, Future}
import scala.util.Random
@Singleton
class StackController @Inject() (
override val actorSystem: ActorSystem,
override val random:Random = new Random
) extends Controller with MyStackLongElement {
implicit val myExecutionContext: ExecutionContext = actorSystem.dispatcher
def message = AsyncStack { implicit req =>
Future{
val long = getStackedLong()
Ok(long.toString)
}
}
}
先ほど用意したMyStackLongElementをextendsして、任意の場所でgetStackedLongを呼ぶだけです。
(なお、ここではRandomはテストしやすいようにDIする形にしました。)
stackable-controllerの役割はこれでほぼ全部と認識しています。
(cleanupOnSucceeded
あたりはfilterに吸収されそう。。。。)
テストを書く
stackable-controllerを用いていても、普通のControllerと同じようにテストを行えます。
import java.util.concurrent.TimeUnit
import akka.actor.ActorSystem
import akka.util.Timeout
import org.mockito.Mockito._
import org.scalatest.mock.MockitoSugar
import org.scalatest.{FunSpec, MustMatchers}
import play.api.mvc.Result
import play.api.test.{FakeRequest, Helpers}
import scala.concurrent.Future
import scala.util.Random
class StackControllerTest extends FunSpec with MustMatchers with MockitoSugar {
describe("StackControllerTest") {
implicit val timeout = Timeout(5000, TimeUnit.MILLISECONDS)
it("StackController success"){
val randomMock = mock[Random]
when(randomMock.nextLong()) thenReturn 100L
when(randomMock.nextBoolean()) thenReturn false
val actorSystem = ActorSystem.apply()
val controller = new StackController(actorSystem, randomMock)
val result: Future[Result] = controller.message().apply(FakeRequest())
Helpers.contentAsString(result) mustBe "100"
Helpers.status(result) mustBe 200
}
it("StackController fail"){
val randomMock = mock[Random]
when(randomMock.nextLong()) thenReturn 100L
when(randomMock.nextBoolean()) thenReturn true
val actorSystem = ActorSystem.apply()
val controller = new StackController(actorSystem, randomMock)
val result: Future[Result] = controller.message().apply(FakeRequest())
Helpers.status(result) mustBe 400
Helpers.contentAsString(result) mustBe ""
}
}
}
MyStackLongElementは、randomで1/2で失敗するように作っています。
なのでテスト時にはMockを使って成功・失敗をコントロールして動作確認してみました。
なるべくグローバルに依存しないように作れば、このようにテストも容易になります。
まとめ
これだけだとあまり嬉しさがわからないと思うので、play2-authの記事を待つべし。