Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
206 changes: 104 additions & 102 deletions README.md
Original file line number Diff line number Diff line change
@@ -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<Booking> bookings = Booking.all();
Assert.assertEquals("#bookings", 1, bookings.size());
}
List<Booking> 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!
And remember, Play! 2.3.x Functional Tests give you full access to the database!
7 changes: 2 additions & 5 deletions module/README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,3 @@
funcy
=====
# funcy

Page Driven Functional Test for Play 2.0.

Source Code
Source code of Page Driven Functional Test for Play 2.3.x.
2 changes: 1 addition & 1 deletion module/app/funcy/Page.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down
6 changes: 3 additions & 3 deletions module/app/funcy/Tag.java
Original file line number Diff line number Diff line change
Expand Up @@ -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 {

Expand Down Expand Up @@ -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);
}
Expand Down
23 changes: 23 additions & 0 deletions module/build.sbt
Original file line number Diff line number Diff line change
@@ -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:_")
18 changes: 0 additions & 18 deletions module/project/Build.scala

This file was deleted.

2 changes: 1 addition & 1 deletion module/project/build.properties
Original file line number Diff line number Diff line change
@@ -1 +1 @@
sbt.version=0.11.2
sbt.version=0.13.6
8 changes: 7 additions & 1 deletion module/project/plugins.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -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")
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")
19 changes: 19 additions & 0 deletions samples/basic-sample/build.sbt
Original file line number Diff line number Diff line change
@@ -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")
19 changes: 0 additions & 19 deletions samples/basic-sample/project/Build.scala

This file was deleted.

2 changes: 1 addition & 1 deletion samples/basic-sample/project/build.properties
Original file line number Diff line number Diff line change
@@ -1 +1 @@
sbt.version=0.11.2
sbt.version=0.13.6
2 changes: 1 addition & 1 deletion samples/basic-sample/project/plugins.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -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")
addSbtPlugin("com.typesafe.play" % "sbt-plugin" % System.getProperty("play.version", "2.3.5"))
Loading