§ScalaTest を用いたアプリケーションのテスト
アプリケーションのテストを作成することは、複雑なプロセスとなる場合があります。Play はヘルパーとアプリケーションスタブを提供し、ScalaTest は ScalaTest + Play という統合ライブラリを提供することで、アプリケーションのテストを可能な限り簡単にしています。
§概要
テストの場所は “test” フォルダです。
Play コンソールからテストを実行できます。
- すべてのテストを実行するには、
test
を実行します。 - 1 つのテストクラスのみを実行するには、
test-only
の後にクラス名(例:test-only my.namespace.MySpec
)を実行します。 - 失敗したテストのみを実行するには、
test-quick
を実行します。 - テストを継続的に実行するには、コマンドの前にチルダを付けて実行します(例:
~test-quick
)。 - コンソールで
FakeRequest
などのテストヘルパーにアクセスするには、test:console
を実行します。
Play でのテストは SBT に基づいており、詳細な説明は SBT のテストの章にあります。
§ScalaTest + Play の使用
ScalaTest + Play を使用するには、build.sbt
を次のように変更して、ビルドに追加する必要があります。
libraryDependencies ++= Seq(
"org.scalatestplus.play" %% "scalatestplus-play" % "x.x.x" % Test
)
ここで、x.x.x
は scalatestplus-play
アーティファクトの特定のバージョン(例:5.1.0
)です。利用可能なリリースはこちらをご覧ください。
ScalaTest または ScalaTest + mockito をビルドに明示的に追加する必要はありません。ScalaTest の適切なバージョンは、ScalaTest + Play の推移的な依存関係として自動的に取り込まれます。ただし、Play のバージョンに一致する ScalaTest + Play のバージョンを選択する必要があります。これを行うには、ScalaTest + Play の リリース互換性マトリックスを確認してください。
ScalaTest + Play では、PlaySpec
トレイトを拡張してテストクラスを定義します。以下に例を示します。
import org.scalatestplus.play._
import scala.collection.mutable
class StackSpec extends PlaySpec {
"A Stack" must {
"pop values in last-in-first-out order" in {
val stack = new mutable.Stack[Int]
stack.push(1)
stack.push(2)
stack.pop() mustBe 2
stack.pop() mustBe 1
}
"throw NoSuchElementException if an empty stack is popped" in {
val emptyStack = new mutable.Stack[Int]
a[NoSuchElementException] must be thrownBy {
emptyStack.pop()
}
}
}
}
PlaySpec
を使用する代わりに、独自の基底クラスを定義することもできます。
Play 自体、IntelliJ IDEA ( Scala プラグインを使用)、または Eclipse ( Scala IDE および ScalaTest Eclipse プラグインを使用) でテストを実行できます。詳細については、IDE ページをご覧ください。
§マッチャー
PlaySpec
は ScalaTest の MustMatchers
をミックスインしているため、ScalaTest のマッチャー DSL を使用してアサーションを記述できます。
import play.api.test.Helpers._
"Hello world" must endWith ("world")
詳細については、MustMatchers
のドキュメントを参照してください。
§Mockito
モックを使用すると、外部依存関係に対するユニットテストを分離できます。たとえば、クラスが外部の DataService
クラスに依存している場合、DataService
オブジェクトをインスタンス化せずに、適切なデータをクラスに供給できます。
ScalaTest は、Mockito との統合を MockitoSugar
トレイトを介して提供します。
Mockito を使用するには、MockitoSugar
をテストクラスにミックスインし、Mockito ライブラリを使用して依存関係をモックします。
case class Data(retrievalDate: java.util.Date)
trait DataService {
def findData: Data
}
import org.scalatestplus.mockito.MockitoSugar
import org.scalatestplus.play._
import org.mockito.Mockito._
class ExampleMockitoSpec extends PlaySpec with MockitoSugar {
"MyService#isDailyData" should {
"return true if the data is from today" in {
val mockDataService = mock[DataService]
when(mockDataService.findData).thenReturn(Data(new java.util.Date()))
val myService = new MyService() {
override def dataService = mockDataService
}
val actual = myService.isDailyData
actual mustBe true
}
}
}
モックは、クラスのパブリックメソッドをテストするのに特に役立ちます。オブジェクトやプライベートメソッドのモックも可能ですが、かなり難しくなります。
§モデルのユニットテスト
Play では、モデルが特定のデータベースデータアクセスレイヤーを使用する必要はありません。ただし、アプリケーションが Anorm または Slick を使用している場合、多くの場合、モデルは内部的にデータベースアクセスへの参照を持ちます。
import anorm._
import anorm.SqlParser._
case class User(id: String, name: String, email: String) {
def roles = DB.withConnection { implicit connection =>
...
}
}
ユニットテストの場合、このアプローチでは roles
メソッドをモックするのが難しい場合があります。
一般的なアプローチは、モデルをデータベースおよびできるだけ多くのロジックから分離し、リポジトリレイヤーの背後にデータベースアクセスを抽象化することです。
case class Role(name: String)
case class User(id: String, name: String, email: String)
trait UserRepository {
def roles(user: User): Set[Role]
}
class AnormUserRepository extends UserRepository {
import anorm._
import anorm.SqlParser._
def roles(user:User) : Set[Role] = {
...
}
}
次に、サービスを介してアクセスします。
class UserService(userRepository: UserRepository) {
def isAdmin(user: User): Boolean = {
userRepository.roles(user).contains(Role("ADMIN"))
}
}
このようにして、isAdmin
メソッドは、UserRepository
参照をモックし、それをサービスに渡すことでテストできます。
class UserServiceSpec extends PlaySpec with MockitoSugar {
"UserService#isAdmin" should {
"be true when the role is admin" in {
val userRepository = mock[UserRepository]
when(userRepository.roles(any[User])).thenReturn(Set(Role("ADMIN")))
val userService = new UserService(userRepository)
val actual = userService.isAdmin(User("11", "Steve", "[email protected]"))
actual mustBe true
}
}
}
§コントローラーのユニットテスト
コントローラーは通常のクラスであるため、Play ヘルパーを使用して簡単にユニットテストできます。コントローラーが別のクラスに依存している場合は、依存性注入を使用すると、これらの依存関係をモックできます。たとえば、次のコントローラーがあるとします。
class ExampleController(val controllerComponents: ControllerComponents) extends BaseController {
def index() = Action {
Ok("ok")
}
}
次のようにテストできます。
import scala.concurrent.Future
import org.scalatestplus.play._
import play.api.mvc._
import play.api.test._
import play.api.test.Helpers._
class ExampleControllerSpec extends PlaySpec with Results {
"Example Page#index" should {
"should be valid" in {
val controller = new ExampleController(Helpers.stubControllerComponents())
val result: Future[Result] = controller.index().apply(FakeRequest())
val bodyText: String = contentAsString(result)
bodyText mustBe "ok"
}
}
}
§EssentialAction のユニットテスト
Action
または Filter
のテストでは、EssentialAction
をテストする必要がある場合があります ( EssentialAction の詳細についてはこちら)。
このためには、テスト Helpers.call
を次のように使用できます。
class ExampleEssentialActionSpec extends PlaySpec with GuiceOneAppPerSuite {
implicit lazy val materializer: Materializer = app.materializer
implicit lazy val Action: DefaultActionBuilder = app.injector.instanceOf(classOf[DefaultActionBuilder])
"An essential action" should {
"can parse a JSON body" in {
val action: EssentialAction = Action { request =>
val value = (request.body.asJson.get \ "field").as[String]
Ok(value)
}
val request = FakeRequest(POST, "/").withJsonBody(Json.parse("""{ "field": "value" }"""))
val result = call(action, request)
status(result) mustEqual OK
contentAsString(result) mustEqual "value"
}
}
}
このドキュメントにエラーを見つけましたか?このページのソースコードはこちらにあります。 ドキュメントのガイドラインを読んだ後、プルリクエストを送信してください。質問や共有するアドバイスはありますか?コミュニティフォーラムにアクセスして、コミュニティとの会話を始めましょう。