PageRenderTime 49ms CodeModel.GetById 22ms RepoModel.GetById 0ms app.codeStats 0ms

/documentation/manual/working/scalaGuide/main/tests/ScalaTestingWithSpecs2.md

https://github.com/playframework/playframework
Markdown | 172 lines | 97 code | 75 blank | 0 comment | 0 complexity | 5843c06629c05a505901d015177690b5 MD5 | raw file
  1. <!--- Copyright (C) Lightbend Inc. <https://www.lightbend.com> -->
  2. # Testing your application with specs2
  3. Writing tests for your application can be an involved process. Play provides a default test framework for you, and provides helpers and application stubs to make testing your application as easy as possible.
  4. ## Overview
  5. The location for tests is in the "test" folder. There are two sample test files created in the test folder which can be used as templates.
  6. You can run tests from the Play console.
  7. * To run all tests, run `test`.
  8. * To run only one test class, run `test-only` followed by the name of the class i.e. `test-only my.namespace.MySpec`.
  9. * To run only the tests that have failed, run `test-quick`.
  10. * To run tests continually, run a command with a tilde in front, i.e. `~test-quick`.
  11. * To access test helpers such as `FakeRequest` in console, run `Test/console`.
  12. Testing in Play is based on sbt, and a full description is available in the [testing sbt](https://www.scala-sbt.org/1.x/docs/Testing.html) chapter.
  13. ## Using specs2
  14. To use Play's specs2 support, add the Play specs2 dependency to your build as a test scoped dependency:
  15. ```scala
  16. libraryDependencies += specs2 % Test
  17. ```
  18. In [specs2](https://etorreborre.github.io/specs2/), tests are organized into specifications, which contain examples which run the system under test through various different code paths.
  19. Specifications extend the [`Specification`](https://etorreborre.github.io/specs2/api/SPECS2-3.6.6/index.html#org.specs2.mutable.Specification) trait and are using the should/in format:
  20. @[scalatest-helloworldspec](code/specs2/HelloWorldSpec.scala)
  21. Specifications can be run in either IntelliJ IDEA (using the [Scala plugin](https://blog.jetbrains.com/scala/)) or in Eclipse (using the [Scala IDE](http://scala-ide.org/)). Please see the [[IDE page|IDE]] for more details.
  22. > **Note:** Due to a bug in the [presentation compiler](https://scala-ide-portfolio.assembla.com/spaces/scala-ide/support/tickets/1001843-specs2-tests-with-junit-runner-are-not-recognized-if-there-is-package-directory-mismatch#/activity/ticket:), tests must be defined in a specific format to work with Eclipse:
  23. * The package must be exactly the same as the directory path.
  24. * The specification must be annotated with `@RunWith(classOf[JUnitRunner])`.
  25. Here is a valid specification for Eclipse:
  26. @[basic-spec](code/models/UserSpec.scala)
  27. ### Matchers
  28. When you use an example, you must return an example result. Usually, you will see a statement containing a `must`:
  29. @[assertion-example](code/models/UserSpec.scala)
  30. The expression that follows the `must` keyword are known as [`matchers`](https://etorreborre.github.io/specs2/guide/SPECS2-3.6.6/org.specs2.guide.Matchers.html). Matchers return an example result, typically Success or Failure. The example will not compile if it does not return a result.
  31. The most useful matchers are the [match results](https://etorreborre.github.io/specs2/guide/SPECS2-3.6.6/org.specs2.guide.Matchers.html#out-of-the-box). These are used to check for equality, determine the result of Option and Either, and even check if exceptions are thrown.
  32. There are also [optional matchers](https://etorreborre.github.io/specs2/guide/SPECS2-3.6.6/org.specs2.guide.Matchers.html#optional) that allow for XML and JSON matching in tests.
  33. ### Mockito
  34. Mocks are used to isolate unit tests against external dependencies. For example, if your class depends on an external `DataService` class, you can feed appropriate data to your class without instantiating a `DataService` object.
  35. [Mockito](https://github.com/mockito/mockito) is integrated into specs2 as the default [mocking library](https://etorreborre.github.io/specs2/guide/SPECS2-3.6.6/org.specs2.guide.UseMockito.html).
  36. To use Mockito, add the following import:
  37. @[import-mockito](code/models/UserSpec.scala)
  38. You can mock out references to classes like so:
  39. @[specs2-mockito-dataservice](code/specs2/ExampleMockitoSpec.scala)
  40. @[specs2-mockito](code/specs2/ExampleMockitoSpec.scala)
  41. Mocking is especially useful for testing the public methods of classes. Mocking objects and private methods is possible, but considerably harder.
  42. ## Unit Testing Models
  43. Play does not require models to use a particular database data access layer. However, if the application uses Anorm or Slick, then frequently the Model will have a reference to database access internally.
  44. ```scala
  45. import anorm._
  46. import anorm.SqlParser._
  47. case class User(id: String, name: String, email: String) {
  48. def roles = DB.withConnection { implicit connection =>
  49. ...
  50. }
  51. }
  52. ```
  53. For unit testing, this approach can make mocking out the `roles` method tricky.
  54. A common approach is to keep the models isolated from the database and as much logic as possible, and abstract database access behind a repository layer.
  55. @[scalatest-models](code/models/User.scala)
  56. @[scalatest-repository](code/services/UserRepository.scala)
  57. ```scala
  58. class AnormUserRepository extends UserRepository {
  59. import anorm._
  60. import anorm.SqlParser._
  61. def roles(user:User) : Set[Role] = {
  62. ...
  63. }
  64. }
  65. ```
  66. and then access them through services:
  67. @[scalatest-userservice](code/services/UserService.scala)
  68. In this way, the `isAdmin` method can be tested by mocking out the `UserRepository` reference and passing it into the service:
  69. @[scalatest-userservicespec](code/specs2/UserServiceSpec.scala)
  70. ## Unit Testing Controllers
  71. Since your controllers are just regular classes, you can easily unit test them using Play helpers. If your controllers depends on another classes, using [[dependency injection|ScalaDependencyInjection]] will enable you to mock these dependencies. Per instance, given the following controller:
  72. @[scalatest-examplecontroller](code/specs2/ExampleControllerSpec.scala)
  73. You can test it like:
  74. @[scalatest-examplecontrollerspec](code/specs2/ExampleControllerSpec.scala)
  75. ### StubControllerComponents
  76. The [`StubControllerComponentsFactory`](api/scala/play/api/test/StubControllerComponentsFactory.html) creates a stub [`ControllerComponents`](api/scala/play/api/mvc/ControllerComponents.html) that can be used for unit testing a controller:
  77. @[scalatest-stubcontrollercomponents](code/specs2/ExampleHelpersSpec.scala)
  78. ### StubBodyParser
  79. The [`StubBodyParserFactory`](api/scala/play/api/test/StubBodyParserFactory.html) creates a stub [`BodyParser`](api/scala/play/api/mvc/BodyParser.html) that can be used for unit testing content:
  80. @[scalatest-stubbodyparser](code/specs2/ExampleHelpersSpec.scala)
  81. ## Unit Testing Forms
  82. Forms are also just regular classes, and can unit tested using Play's Test Helpers. Using [`FakeRequest`](api/scala/play/api/test/FakeRequest.html), you can call `form.bindFromRequest` and test for errors against any custom constraints.
  83. To unit test form processing and render validation errors, you will want a [`MessagesApi`](api/scala/play/api/i18n/MessagesApi.html) instance in implicit scope. The default implementation of [`MessagesApi`](api/scala/play/api/i18n/MessagesApi.html) is [`DefaultMessagesApi`](api/scala/play/api/i18n/DefaultMessagesApi.html):
  84. You can test it like:
  85. @[scalatest-exampleformspec](code/specs2/ExampleControllerSpec.scala)
  86. When rendering a template that takes form helpers, you can pass in a Messages the same way, or use [`Helpers.stubMessages()`](api/scala/play/api/test/Helpers$.html#stubMessages\(messagesApi:play.api.i18n.MessagesApi,requestHeader:play.api.mvc.RequestHeader\):play.api.i18n.Messages):
  87. @[scalatest-exampletemplatespec](code/specs2/ExampleControllerSpec.scala)
  88. Or, if you are using a form that uses `CSRF.formField` and requires an implicit request, you can use [`MessagesRequest`](api/scala/play/api/mvc/MessagesRequest.html) in the template and use [`Helpers.stubMessagesRequest()`](api/scala/play/api/test/Helpers$.html#stubMessagesRequest\(messagesApi:play.api.i18n.MessagesApi,request:play.api.mvc.Request[play.api.mvc.AnyContentAsEmpty.type]\):play.api.mvc.MessagesRequest[play.api.mvc.AnyContentAsEmpty.type]):
  89. @[scalatest-examplecsrftemplatespec](code/specs2/ExampleControllerSpec.scala)
  90. ## Unit Testing EssentialAction
  91. Testing [`Action`](api/scala/play/api/mvc/Action.html) or [`Filter`](api/scala/play/api/mvc/Filter.html) can require to test an [`EssentialAction`](api/scala/play/api/mvc/EssentialAction.html) ([[more information about what an EssentialAction is|ScalaEssentialAction]])
  92. For this, the test [`Helpers.call()`](api/scala/play/api/test/Helpers$.html#call[T]\(action:play.api.mvc.EssentialAction,rh:play.api.mvc.RequestHeader,body:T\)\(implicitw:play.api.http.Writeable[T],implicitmat:akka.stream.Materializer\):scala.concurrent.Future[play.api.mvc.Result]) can be used like that:
  93. @[scalatest-exampleessentialactionspec](code/specs2/ExampleEssentialActionSpec.scala)
  94. ## Unit Testing Messages
  95. For unit testing purposes, [`DefaultMessagesApi`](api/scala/play/api/i18n/DefaultMessagesApi.html) can be instantiated without arguments, and will take a raw map, so you can test forms and validation failures against custom [`MessagesApi`](api/scala/play/api/i18n/MessagesApi.html):
  96. @[scalatest-examplemessagesspec](code/specs2/ExampleMessagesSpec.scala)
  97. You can also use [`Helpers.stubMessagesApi()`](api/scala/play/api/test/Helpers$.html#stubMessagesApi\(messages:Map[String,Map[String,String]],langs:play.api.i18n.Langs,langCookieName:String,langCookieSecure:Boolean,langCookieHttpOnly:Boolean,langCookieSameSite:Option[play.api.mvc.Cookie.SameSite],httpConfiguration:play.api.http.HttpConfiguration,langCookieMaxAge:Option[Int]\):play.api.i18n.MessagesApi) in testing to provide a premade empty [`MessagesApi`](api/scala/play/api/i18n/MessagesApi.html).