From 563122682a0a87b2372a37dad0461175fd1c959b Mon Sep 17 00:00:00 2001 From: Megazord Date: Fri, 17 Oct 2014 16:58:48 -0300 Subject: [PATCH 01/10] Better formatting and small corrections --- README.md | 208 ++++++++++++++++++++++++++++-------------------------- 1 file changed, 106 insertions(+), 102 deletions(-) diff --git a/README.md b/README.md index 353bf59..abf0f42 100644 --- a/README.md +++ b/README.md @@ -1,130 +1,134 @@ -funcy -===== +# funcy -Page Driven Functional Tests for Play 2.0. +Page Driven Functional Tests for Play 2.3.x -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_2.9.1" % "0.1" % "test", + "org.jsoup" % "jsoup" % "1.6.2" % "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_2.9.1" % "0.1" % "test", + "org.jsoup" % "jsoup" % "1.6.2" % "test" +) -Page ----- +val main = PlayProject(appName, appVersion, appDependencies, mainLang = JAVA).settings( + resolvers += Resolver.url("Joerg Violas Repository", url("http://www.joergviola.de/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! \ No newline at end of file From bfe83b51f0eadcffec85ae33502718dd7a4ae78f Mon Sep 17 00:00:00 2001 From: Megazord Date: Fri, 17 Oct 2014 17:13:15 -0300 Subject: [PATCH 02/10] Updates sbt version to 0.13.6 --- module/project/build.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 From 38a90dc1e82895cdbf921deaae3b7f7e5f618f86 Mon Sep 17 00:00:00 2001 From: Megazord Date: Fri, 17 Oct 2014 17:26:04 -0300 Subject: [PATCH 03/10] Updates to play 2.3.5 1. Uses build.sbt instead of project/Build.scala 2. Updates code to use twirl 3. Updates soup to version 1.8.1 --- module/app/funcy/Page.java | 2 +- module/app/funcy/Tag.java | 6 +++--- module/build.sbt | 21 +++++++++++++++++++++ module/project/Build.scala | 18 ------------------ module/project/plugins.sbt | 4 +++- 5 files changed, 28 insertions(+), 23 deletions(-) create mode 100644 module/build.sbt delete mode 100644 module/project/Build.scala 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..d3d07c7 --- /dev/null +++ b/module/build.sbt @@ -0,0 +1,21 @@ +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.10.4" + +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/plugins.sbt b/module/project/plugins.sbt index 0aea2fe..94e8e0f 100644 --- a/module/project/plugins.sbt +++ b/module/project/plugins.sbt @@ -5,4 +5,6 @@ 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") From 19c047712771ffac0bff62ddef383a2f9b9404ca Mon Sep 17 00:00:00 2001 From: Megazord Date: Fri, 17 Oct 2014 17:26:16 -0300 Subject: [PATCH 04/10] Adds useful plugins --- module/project/plugins.sbt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/module/project/plugins.sbt b/module/project/plugins.sbt index 94e8e0f..840a296 100644 --- a/module/project/plugins.sbt +++ b/module/project/plugins.sbt @@ -8,3 +8,7 @@ resolvers += "Typesafe repository" at "http://repo.typesafe.com/typesafe/release 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 From 94ee6b7a364cad7b9c7e69040623cad1f2084416 Mon Sep 17 00:00:00 2001 From: Megazord Date: Fri, 17 Oct 2014 17:41:23 -0300 Subject: [PATCH 05/10] Links play framework in readme file --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index abf0f42..f32484d 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # funcy -Page Driven Functional Tests for Play 2.3.x +Page Driven Functional Tests for [Play 2.3.x](https://playframework.com/). ## Introduction From b5415e45e5b8897ba53ad1a2820dd39824c48d0b Mon Sep 17 00:00:00 2001 From: Megazord Date: Fri, 17 Oct 2014 17:41:37 -0300 Subject: [PATCH 06/10] Better formatting for module readme --- module/README.md | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) 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 From c80102cfaa92ded7c2dcc65ecd79cda9d1807136 Mon Sep 17 00:00:00 2001 From: Megazord Date: Fri, 17 Oct 2014 17:42:17 -0300 Subject: [PATCH 07/10] Updates sample project to use play 2.3.x --- samples/basic-sample/build.sbt | 19 +++++++++++++++++++ samples/basic-sample/project/Build.scala | 19 ------------------- samples/basic-sample/project/build.properties | 2 +- samples/basic-sample/project/plugins.sbt | 2 +- .../functional/TestApplication.java | 4 ++-- .../test/{ => funcy}/pages/IndexPage.java | 2 +- 6 files changed, 24 insertions(+), 24 deletions(-) create mode 100644 samples/basic-sample/build.sbt delete mode 100644 samples/basic-sample/project/Build.scala rename samples/basic-sample/test/{ => funcy}/functional/TestApplication.java (83%) rename samples/basic-sample/test/{ => funcy}/pages/IndexPage.java (95%) 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; From ad0a00cf1857a8dc28a8862d9e25d8193cc13204 Mon Sep 17 00:00:00 2001 From: Megazord Date: Fri, 17 Oct 2014 17:46:36 -0300 Subject: [PATCH 08/10] Updates scala version to 2.11.3 --- module/build.sbt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/module/build.sbt b/module/build.sbt index d3d07c7..68ccea2 100644 --- a/module/build.sbt +++ b/module/build.sbt @@ -9,7 +9,8 @@ resolvers ++= Seq( "Typesafe Maven Repository" at "http://repo.typesafe.com/typesafe/maven-releases/" ) -scalaVersion := "2.10.4" +scalaVersion := "2.11.3" + lazy val root = (project in file(".")).enablePlugins(PlayJava) From 419ec051b6eaf28369e1fb40ffeee741549bf8b9 Mon Sep 17 00:00:00 2001 From: Megazord Date: Fri, 17 Oct 2014 17:46:59 -0300 Subject: [PATCH 09/10] Supports both versions of scala: 2.10 and 2.11 --- module/build.sbt | 1 + 1 file changed, 1 insertion(+) diff --git a/module/build.sbt b/module/build.sbt index 68ccea2..3582cf8 100644 --- a/module/build.sbt +++ b/module/build.sbt @@ -11,6 +11,7 @@ resolvers ++= Seq( scalaVersion := "2.11.3" +crossScalaVersions := Seq("2.10.4", "2.11.3") lazy val root = (project in file(".")).enablePlugins(PlayJava) From 6b04d39dcd0b3cb651b6e87e69aee1e76b854ce9 Mon Sep 17 00:00:00 2001 From: Megazord Date: Sun, 2 Nov 2014 22:15:11 -0200 Subject: [PATCH 10/10] Small corrections --- README.md | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index f32484d..75052cc 100644 --- a/README.md +++ b/README.md @@ -46,8 +46,7 @@ Don't forget to add my module repository as well: resolvers += Resolver.url("Edulify Repository", url("http://edulify.github.io/modules/releases/"))(Resolver.ivyStylePatterns) libraryDependencies ++= Seq( - "funcy" % "funcy_2.9.1" % "0.1" % "test", - "org.jsoup" % "jsoup" % "1.6.2" % "test" + "funcy" %% "funcy" % "0.3" % "test" ) ``` @@ -59,12 +58,11 @@ Don't forget to add my module repository as well: ```scala val appDependencies = Seq( - "funcy" % "funcy_2.9.1" % "0.1" % "test", - "org.jsoup" % "jsoup" % "1.6.2" % "test" + "funcy" %% "funcy" % "0.3" % "test" ) val main = PlayProject(appName, appVersion, appDependencies, mainLang = JAVA).settings( - resolvers += Resolver.url("Joerg Violas Repository", url("http://www.joergviola.de/releases/"))(Resolver.ivyStylePatterns), + resolvers += Resolver.url("Edulify Repository", url("http://edulify.github.io/modules/releases/"))(Resolver.ivyStylePatterns) ) ``` @@ -131,4 +129,4 @@ public void testIndex() { } ``` -And remember, Play! 2.3.x 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!