ドキュメント

§ScalaTest を用いたアプリケーションのテスト

アプリケーションのテストを作成することは、複雑なプロセスとなる場合があります。Play はヘルパーとアプリケーションスタブを提供し、ScalaTest は ScalaTest + Play という統合ライブラリを提供することで、アプリケーションのテストを可能な限り簡単にしています。

§概要

テストの場所は “test” フォルダです。

Play コンソールからテストを実行できます。

Play でのテストは SBT に基づいており、詳細な説明は SBT のテストの章にあります。

§ScalaTest + Play の使用

ScalaTest + Play を使用するには、build.sbt を次のように変更して、ビルドに追加する必要があります。

libraryDependencies ++= Seq(
  "org.scalatestplus.play" %% "scalatestplus-play" % "x.x.x" % Test
)

ここで、x.x.xscalatestplus-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"
    }
  }
}

次へ: ScalaTest を用いた機能テストの記述


このドキュメントにエラーを見つけましたか?このページのソースコードはこちらにあります。 ドキュメントのガイドラインを読んだ後、プルリクエストを送信してください。質問や共有するアドバイスはありますか?コミュニティフォーラムにアクセスして、コミュニティとの会話を始めましょう。