diff --git a/README.md b/README.md index 353bf59..75052cc 100644 --- a/README.md +++ b/README.md @@ -1,130 +1,132 @@ -funcy -===== +# funcy -Page Driven Functional Tests for Play 2.0. +Page Driven Functional Tests for [Play 2.3.x](https://playframework.com/). -Introduction ------------- +## Introduction -Functional Test in Play 2.0 are the best way to test web applications: +Functional Test in Play 2.3.x are the best way to test web applications: * In standard web applications, you simply do not have really complex business logic. There is no point in using unit tests. -* Selenium tests are hard to setup (even with Plays support), require explizit fixtures and they are very slow - too slow to be run before each commit. +* [Selenium](http://www.seleniumhq.org/) tests are hard to setup (even with Plays support), require explizit fixtures and they are very slow - too slow to be run before each commit. -Example: Booking a ticket is fairly easy: Simply write a row into a database. -Whats the point of writing a unit test for that? +Example: Booking a ticket is fairly easy: Simply write a row into a database. Whats the point of writing a unit test for that? -Building the booking form, receiving the request, validating user input, creating the ticket, -sending the confirmation mail and displaying the confirmation page, on the other hand, is quite a -complex process. +Building the booking form, receiving the request, validating user input, creating the ticket, sending the confirmation mail and displaying the confirmation page, on the other hand, is quite a complex process. -But testing it through selenium is hard and , if you run into the trap of recording your test cases, -very prone to changes of the underlying software. +But testing it through selenium is hard and , if you run into the trap of recording your test cases, very prone to changes of the underlying software. Using Page Driven Play 2.3.x Functional Tests provided by funcy, you can: -Using Page Driven Play 2.0 Functional Tests provided by funcy, you can -* write your tests as simple unit tests -* check results by accessing resulting web page doms or the database directly -* run them very fast against an in-memory database. +* Write your tests as simple unit tests +* Check results by accessing resulting web page doms or the database directly +* Run them very fast against an in-memory database. Consider this example: - @Test - public void testBooking() { - IndexPage indexPage = new IndexPage(); - EventPage eventPage = indexPage.clickEvent("Sidney Opera"); - BookingPage bookingPage = eventPage.book("2012/05/07"); - ConfirmationPage confirmationPage = bookingPage.book(); +```java +@Test +public void testBooking() { + IndexPage indexPage = new IndexPage(); + EventPage eventPage = indexPage.clickEvent("Sidney Opera"); + BookingPage bookingPage = eventPage.book("2012/05/07"); + ConfirmationPage confirmationPage = bookingPage.book(); - List bookings = Booking.all(); - Assert.assertEquals("#bookings", 1, bookings.size()); - } + List bookings = Booking.all(); + Assert.assertEquals("#bookings", 1, bookings.size()); +} +``` -Installation ------------- +## Installation -In your `Build.scala` add the dependencies. Use the test scope, because funcy is not required -at runtime. You have to include the dependency for jsoup as well, because I did not yet -succeed in building the module so that this dependency is transported to the app in test scope -(solution, anybody?). +### Using `build.sbt`: + +In your `build.sbt` add the dependencies. Use the test scope, because funcy is not required at runtime. You have to include the dependency for jsoup as well, because I did not yet succeed in building the module so that this dependency is transported to the app in test scope (solution, anybody?). Don't forget to add my module repository as well: - val appDependencies = Seq( - "funcy" % "funcy_2.9.1" % "0.1" % "test", - "org.jsoup" % "jsoup" % "1.6.2" % "test" - ) +```scala +resolvers += Resolver.url("Edulify Repository", url("http://edulify.github.io/modules/releases/"))(Resolver.ivyStylePatterns) + +libraryDependencies ++= Seq( + "funcy" %% "funcy" % "0.3" % "test" +) +``` + +### Using `project/Build.scala`: - val main = PlayProject(appName, appVersion, appDependencies, mainLang = JAVA).settings( - resolvers += Resolver.url("Joerg Violas Repository", url("http://www.joergviola.de/releases/"))(Resolver.ivyStylePatterns), - ) +In your `Build.scala` add the dependencies. Use the test scope, because funcy is not required at runtime. You have to include the dependency for jsoup as well, because I did not yet succeed in building the module so that this dependency is transported to the app in test scope (solution, anybody?). + +Don't forget to add my module repository as well: +```scala +val appDependencies = Seq( + "funcy" %% "funcy" % "0.3" % "test" +) -Page ----- +val main = PlayProject(appName, appVersion, appDependencies, mainLang = JAVA).settings( + resolvers += Resolver.url("Edulify Repository", url("http://edulify.github.io/modules/releases/"))(Resolver.ivyStylePatterns) +) +``` + +## Page Create a subclass of `Page` for each page of your application. Consider the following example: - package pages; - - import play.mvc.Result; - import funcy.Form; - import funcy.Page; - - public class IndexPage extends Page { - - public IndexPage(Result result) { - super(result, "/"); - } - - public IndexPage save(String msg) { - Form form = form(0); - form.set("test", msg); - return new IndexPage(form.submitName("Save")); - } - - public IndexPage clickReload() { - return new IndexPage(clickName("Reload")); - } - } - -Typically a `Page` is constructed from a `play.mvc.Result`. The super-constructor expects -a regexp additionally. The current URL is matched against this URL to assert whether -the test is on the right page. - -Your `Page` class should exhibit a public method for each action a user can perform on -the page. That is the core of the Page Driver pattern: Create an abstract model of the page -under test so that changes do not affect the test at all or can be represented by a change -of the Page Drivers public contract, which is obvioulsy under good control. - -`Page` has a lot of methods available to perform actions (most notably, `clickName()`) -and to access it contents (e.g. `form()`, `getElementsByTag()`). - -The latter are inherited from `Tag`, which provides access to the `Page`'s DOM. -JSoup is used for HTML parsing. Using `Tag`, you can search for DOM elements in a flexible -way. A good example is the implementation of the link part of `clickName()`: - - public String clickLinkName(String name, int i) { - TagList list = getElementsByTag("a").text(name); - if (i >= list.size()) - return null; - Tag link = list.get(i); - String href = link.attr("href"); - return href; - } - -FunctionalTest --------------- - -Let your test classes inherit `FunctionalTest`. This way, a `fakeApplication` is started, -provisioned with `application.conf` and stopped automatically. +```java +package pages; + +import play.mvc.Result; +import funcy.Form; +import funcy.Page; + +public class IndexPage extends Page { + + public IndexPage(Result result) { + super(result, "/"); + } + + public IndexPage save(String msg) { + Form form = form(0); + form.set("test", msg); + return new IndexPage(form.submitName("Save")); + } + + public IndexPage clickReload() { + return new IndexPage(clickName("Reload")); + } +} +``` + +Typically a `Page` is constructed from a `play.mvc.Result`. The super-constructor expects a regexp additionally. The current URL is matched against this URL to assert whether the test is on the right page. + +Your `Page` class should exhibit a public method for each action a user can perform on the page. That is the core of the Page Driver pattern: Create an abstract model of the page under test so that changes do not affect the test at all or can be represented by a change of the Page Drivers public contract, which is obvioulsy under good control. + +`Page` has a lot of methods available to perform actions (most notably, `clickName()`) and to access it contents (e.g. `form()`, `getElementsByTag()`). + +The latter are inherited from `Tag`, which provides access to the `Page`'s DOM. JSoup is used for HTML parsing. Using `Tag`, you can search for DOM elements in a flexible way. A good example is the implementation of the link part of `clickName()`: + +```java +public String clickLinkName(String name, int i) { + TagList list = getElementsByTag("a").text(name); + if (i >= list.size()) + return null; + Tag link = list.get(i); + String href = link.attr("href"); + return href; +} +``` + +## FunctionalTest + +Let your test classes inherit `FunctionalTest`. This way, a `fakeApplication` is started, provisioned with `application.conf` and stopped automatically. In your test methods, use the `Page`s and their public API: - @Test - public void testIndex() { - IndexPage indexPage = new IndexPage(Page.get("/")); - indexPage = indexPage.save("Perform Test"); - indexPage = indexPage.clickReload(); - } +```java +@Test +public void testIndex() { + IndexPage indexPage = new IndexPage(Page.get("/")); + indexPage = indexPage.save("Perform Test"); + indexPage = indexPage.clickReload(); +} +``` -And remember, Play! 2.0 Functional Tests give you full access to the database! \ No newline at end of file +And remember, Play! 2.3.x Functional Tests give you full access to the database! diff --git a/module/README.md b/module/README.md index 5dbf971..62c7728 100644 --- a/module/README.md +++ b/module/README.md @@ -1,6 +1,3 @@ -funcy -===== +# funcy -Page Driven Functional Test for Play 2.0. - -Source Code \ No newline at end of file +Source code of Page Driven Functional Test for Play 2.3.x. \ No newline at end of file diff --git a/module/app/funcy/Page.java b/module/app/funcy/Page.java index 613fdfd..e6f8394 100644 --- a/module/app/funcy/Page.java +++ b/module/app/funcy/Page.java @@ -19,7 +19,7 @@ import junit.framework.Assert; import junit.framework.Test; -import org.apache.commons.lang.StringUtils; +import org.apache.commons.lang3.StringUtils; import org.jsoup.Jsoup; import org.jsoup.nodes.Element; diff --git a/module/app/funcy/Tag.java b/module/app/funcy/Tag.java index 4764a86..ad306b8 100644 --- a/module/app/funcy/Tag.java +++ b/module/app/funcy/Tag.java @@ -8,12 +8,12 @@ import junit.framework.Assert; -import org.apache.commons.lang.StringEscapeUtils; +import org.apache.commons.lang3.StringEscapeUtils; import org.jsoup.nodes.Element; import play.i18n.Lang; import play.mvc.Result; -import play.templates.ScalaTemplateCompiler.Template; +import play.twirl.api.Content; public class Tag { @@ -106,7 +106,7 @@ public void contains(String string) { public void containsMessage(String code) { String string = play.i18n.Messages.get(Lang.forCode("de"), code); - string = StringEscapeUtils.escapeHtml(string); + string = StringEscapeUtils.escapeHtml4(string); Assert.assertFalse("content does not include message '" + code + "'", html().indexOf(string) == -1); } diff --git a/module/build.sbt b/module/build.sbt new file mode 100644 index 0000000..3582cf8 --- /dev/null +++ b/module/build.sbt @@ -0,0 +1,23 @@ +name := "funcy" + +organization := "funcy" + +version := "0.3" + +resolvers ++= Seq( + "Typesafe repository" at "http://repo.typesafe.com/typesafe/releases/", + "Typesafe Maven Repository" at "http://repo.typesafe.com/typesafe/maven-releases/" +) + +scalaVersion := "2.11.3" + +crossScalaVersions := Seq("2.10.4", "2.11.3") + +lazy val root = (project in file(".")).enablePlugins(PlayJava) + +libraryDependencies ++= Seq( + "com.typesafe.play" %% "play-test" % "2.3.5", + "org.jsoup" % "jsoup" % "1.8.1" +) + +scalacOptions ++= Seq("-unchecked", "-deprecation", "-language:_") \ No newline at end of file diff --git a/module/project/Build.scala b/module/project/Build.scala deleted file mode 100644 index 8bd0b80..0000000 --- a/module/project/Build.scala +++ /dev/null @@ -1,18 +0,0 @@ -import sbt._ -import Keys._ -import PlayProject._ - -object ApplicationBuild extends Build { - - val appName = "funcy" - val appVersion = "0.2" - - val appDependencies = Seq( - "play" % "play-test_2.9.1" % "2.0.1", - "org.jsoup" % "jsoup" % "1.6.2" - ) - - val main = PlayProject(appName, appVersion, appDependencies).settings( - ) - -} diff --git a/module/project/build.properties b/module/project/build.properties index f4ff7a5..64abd37 100644 --- a/module/project/build.properties +++ b/module/project/build.properties @@ -1 +1 @@ -sbt.version=0.11.2 +sbt.version=0.13.6 diff --git a/module/project/plugins.sbt b/module/project/plugins.sbt index 0aea2fe..840a296 100644 --- a/module/project/plugins.sbt +++ b/module/project/plugins.sbt @@ -5,4 +5,10 @@ logLevel := Level.Warn resolvers += "Typesafe repository" at "http://repo.typesafe.com/typesafe/releases/" // Use the Play sbt plugin for Play projects -addSbtPlugin("play" % "sbt-plugin" % "2.0.1") \ No newline at end of file +addSbtPlugin("com.typesafe.play" % "sbt-plugin" % System.getProperty("play.version", "2.3.5")) + +addSbtPlugin("com.typesafe.sbt" % "sbt-twirl" % "1.0.2") + +addSbtPlugin("com.orrsella" % "sbt-stats" % "1.0.5") + +addSbtPlugin("com.timushev.sbt" % "sbt-updates" % "0.1.6") \ No newline at end of file diff --git a/samples/basic-sample/build.sbt b/samples/basic-sample/build.sbt new file mode 100644 index 0000000..0b8b505 --- /dev/null +++ b/samples/basic-sample/build.sbt @@ -0,0 +1,19 @@ +name := "basic-sample" + +version := "1.0-SNAPSHOT" + +resolvers ++= Seq( + "Typesafe repository" at "http://repo.typesafe.com/typesafe/releases/", + "Typesafe Maven Repository" at "http://repo.typesafe.com/typesafe/maven-releases/", + Resolver.url("Edulify Repository", url("http://edulify.github.io/modules/releases/"))(Resolver.ivyStylePatterns) +) + +libraryDependencies ++= Seq( + javaCore, + "funcy" %% "funcy" % "0.3" % "test", + "org.jsoup" % "jsoup" % "1.8.1" % "test" +) + +lazy val root = (project in file(".")).enablePlugins(PlayJava) + +testOptions += Tests.Argument(TestFrameworks.JUnit, "-v", "-q") \ No newline at end of file diff --git a/samples/basic-sample/project/Build.scala b/samples/basic-sample/project/Build.scala deleted file mode 100644 index 8456a77..0000000 --- a/samples/basic-sample/project/Build.scala +++ /dev/null @@ -1,19 +0,0 @@ -import sbt._ -import Keys._ -import PlayProject._ - -object ApplicationBuild extends Build { - - val appName = "basic-sample" - val appVersion = "1.0-SNAPSHOT" - - val appDependencies = Seq( - "funcy" % "funcy_2.9.1" % "0.1" % "test", - "org.jsoup" % "jsoup" % "1.6.2" % "test" - ) - - val main = PlayProject(appName, appVersion, appDependencies, mainLang = JAVA).settings( - resolvers += Resolver.url("Joerg Violas Repository", url("http://www.joergviola.de/releases/"))(Resolver.ivyStylePatterns), - ) - -} diff --git a/samples/basic-sample/project/build.properties b/samples/basic-sample/project/build.properties index f4ff7a5..df58110 100644 --- a/samples/basic-sample/project/build.properties +++ b/samples/basic-sample/project/build.properties @@ -1 +1 @@ -sbt.version=0.11.2 +sbt.version=0.13.6 \ No newline at end of file diff --git a/samples/basic-sample/project/plugins.sbt b/samples/basic-sample/project/plugins.sbt index 1fae0ec..557766e 100644 --- a/samples/basic-sample/project/plugins.sbt +++ b/samples/basic-sample/project/plugins.sbt @@ -5,4 +5,4 @@ logLevel := Level.Warn resolvers += "Typesafe repository" at "http://repo.typesafe.com/typesafe/releases/" // Use the Play sbt plugin for Play projects -addSbtPlugin("play" % "sbt-plugin" % "2.0.1") \ No newline at end of file +addSbtPlugin("com.typesafe.play" % "sbt-plugin" % System.getProperty("play.version", "2.3.5")) \ No newline at end of file diff --git a/samples/basic-sample/test/functional/TestApplication.java b/samples/basic-sample/test/funcy/functional/TestApplication.java similarity index 83% rename from samples/basic-sample/test/functional/TestApplication.java rename to samples/basic-sample/test/funcy/functional/TestApplication.java index 5cd005d..d5a3723 100644 --- a/samples/basic-sample/test/functional/TestApplication.java +++ b/samples/basic-sample/test/funcy/functional/TestApplication.java @@ -1,8 +1,8 @@ -package functional; +package funcy.functional; import org.junit.Test; -import pages.IndexPage; +import funcy.pages.IndexPage; import funcy.FunctionalTest; diff --git a/samples/basic-sample/test/pages/IndexPage.java b/samples/basic-sample/test/funcy/pages/IndexPage.java similarity index 95% rename from samples/basic-sample/test/pages/IndexPage.java rename to samples/basic-sample/test/funcy/pages/IndexPage.java index 7f2daa2..846aaea 100644 --- a/samples/basic-sample/test/pages/IndexPage.java +++ b/samples/basic-sample/test/funcy/pages/IndexPage.java @@ -1,4 +1,4 @@ -package pages; +package funcy.pages; import play.mvc.Result; import funcy.Form;