Intro to Play Framework – Java Edition – Testing



Intro to Play Framework – Java Edition – Testing

0 1


introduction_to_the_play_framework-java


On Github jamesward / introduction_to_the_play_framework-java

Intro to Play Framework

Java Edition

James Ward ~ @_JamesWard

Play Framework

The High Velocity Web Framework for Java & Scala

  • Just hit refresh workflow
  • Type safety
  • RESTful by default
  • Stateless
  • Reactive
  • Asset Compiler
  • First-class JSON

Get Started

Typesafe Activator - Reactive Development Tool for Play

typesafe.com/platform/getstarted

  • 50+ Templates with Tutorials
  • Web UI and CLI
  • Edit Code in Browser, Eclipse, IntelliJ IDEA, or other editor
  • Run App & Tests via Browser
  • Typesafe Console Integration

Create a New Play App

activator new

IDE Support

activator idea

Run the App

activator ~run

Open the App

http://localhost:9000

Routes

Declarative, Type-safe URL Mapping

VERB    PATH                CONTROLLER_METHOD

GET     /                   controllers.Application.index()
GET     /foo                controllers.Application.foo()

Controllers

Receive a Request, Return a Response

public static Result index() {
    return ok(index.render("hello, world"));
}

Views

Composable, Type-safe Scala Templates

@(message: String)
@main("Welcome to Play 2.0") {
    @message
}

Testing

  • Based on JUnit
  • Unit Tests with Mocks
  • Functional Tests for Templates, Controllers, Router

Run All Tests Once

activator test

Run All Tests Continuously

activator ~test

Run One Test

activator "test-only my.namespace.MySpec"

Run Failed Tests

activator test-quick

Test a Template

@Test
public void indexTemplate() {
    Content html = views.html.index.render("test");
    assertThat(contentType(html)).isEqualTo("text/html");
    assertThat(contentAsString(html)).contains("test");
}

Test a Controller

@Test
public void callIndex() {
    running(fakeApplication(), new Runnable() {
        public void run() {
            Result result = callAction(controllers.routes.ref.Application.index());
            assertThat(status(result)).isEqualTo(OK);
            assertThat(contentType(result)).isEqualTo("text/html");
            assertThat(charset(result)).isEqualTo("utf-8");
            assertThat(contentAsString(result)).contains("Your new application is ready.");
        }
    });
}

Test a Route

@Test
public void fooRoute() {
    running(fakeApplication(), new Runnable() {
        public void run() {
            Result result = route(fakeRequest("GET", "/foo"));
            assertThat(result).isNotNull();
        }
    });
}

Models

  • EBean for Java
  • JPA Annotations
  • Versioned Evolution Scripts

conf/application.conf

db.default.driver=org.h2.Driver
db.default.url="jdbc:h2:mem:play"

ebean.default="models.*"

app/models/Bar.java

package models;

import play.db.ebean.Model;

import javax.persistence.Entity;
import javax.persistence.Id;

@Entity
public class Bar extends Model {

    @Id
    public String id;

    public String name;

}

Test a Model

@Test
public void create() {
    running(fakeApplication(), new Runnable() {
        public void run() {
            Bar bar = new Bar();
            bar.name = "Write a test";
            bar.save();
            assertThat(bar.id).isNotNull();
        }
    });
}

Forms

  • Controller: Bind request data to VO
  • View: Server-side Form Helpers

Controller

public static Result addBar() {
    Bar bar = Form.form(Bar.class).bindFromRequest().get();
    bar.save();
    return redirect(routes.Application.index());
}

Route

POST    /bars                   controllers.Application.addBar()

View

@helper.form(action = routes.Application.addBar()) {
    <input name="name"/>
    <input type="submit"/>
}

JSON - Jackson Wrappers

Controller

public static Result getBars() {
    List<Bar> bars = new Model.Finder(String.class, Bar.class).all();
    return ok(toJson(bars));
}

Route

GET     /bars                  controllers.Application.getBars()

Form Validation

Model

@Constraints.Required
public String name;

Controller

public static Result addBar() {
    Form<Bar> form = form(Bar.class).bindFromRequest();
    if (form.hasErrors()) {
        return badRequest(index.render(form));
    }
    else {
        // save
    }
}

Asset Compilers

Unified Client/Server Development

  • assets/javascripts/foo.{js|coffee}
  • assets/stylesheets/foo.less
<script src='@routes.Assets.at("javascripts/foo.min.js")'></script>

CoffeeScript

app/assets/javascripts/index.coffee

$ ->
    $.get "/bars", (data) ->
        $.each data, (index, bar) ->
            $("#bars").append $("<li>").text bar.name

Auto-Minification

<script src='@routes.Assets.at("javascripts/index.min.js")'>

WebJars

The Web in Jars

  • Common client-side libraries: jQuery, Bootstrap, etc
  • Manage versioning through dependency management
  • Transitive dependencies
  • RequireJS support
  • CDN & Cache Friendly
Specify Dependencies in build.sbt
"org.webjars" %% "webjars-play" % "2.2.0",
"org.webjars" % "bootstrap" % "2.3.1"
Create Route for WebJarAssets Controller
GET     /webjars/*file              controllers.WebJarAssets.at(file)
Use a WebJar
<script src='@routes.WebJarAssets.at(WebJarAssets.locate("jquery.min.js"))'></script>

Reactive Requests

Blocking Request

public static Result index() {
    return ok(views.html.index.render("hello"));
}

Async Request

public static F.Promise<Result> index() {
    return F.Promise.promise(new F.Function0<Result>() {
        public Result apply() {
            return ok(views.html.index.render("hello"));
        }
    });
}

Reactive Request (Async + Non-Blocking)

public static F.Promise<Result> index() {
    return F.Promise.delayed(new F.Function0<Result>() {
        public Result apply() throws Throwable {
            return ok(views.html.index.render("hello"));
        }
    }, 5, TimeUnit.SECONDS);
}

Reactive Requests

public static F.Promise<Result> index() {
    F.Promise<WS.Response> jw = WS.url("http://www.jamesward.com").get();
    return jw.map(new F.Function<WS.Response, Result>() {
        public Result apply(WS.Response response) throws Throwable {
            return ok(response.getBody());
        }
    });
}

Reactive Composition

public static F.Promise<Result> index() {
    final F.Promise<WS.Response> jw = WS.url("http://www.jamesward.com").get();
    final F.Promise<WS.Response> tw = WS.url("http://www.twitter.com").get();
    return jw.flatMap(new F.Function<WS.Response, F.Promise<Result>>() {
        public F.Promise<Result> apply(final WS.Response jwR) throws Throwable {
            return tw.map(new F.Function<WS.Response, Result>() {
                public Result apply(WS.Response twR) throws Throwable {
                    return ok(twR.getBody() + jwR.getBody());
                }
            });
        }
    });
}

Reactive Push with SSE

public static Result events() {
    EventSource eventSource = new EventSource() {
        public void onConnected() {
            sendData("hello");
        }
    };
    return ok(eventSource);
}

$ ->
  events = new EventSource("/events")
  events.onmessage = (e) ->
    console.log(e.data)

2-Way Reactive with WebSockets

public static WebSocket<String> echo() {
    return new WebSocket<String>() {
        public void onReady(final In<String> in, final Out<String> out) {
            in.onMessage(new F.Callback<String>() {
                public void invoke(String message) throws Throwable {
                    out.write(message);
                }
            });
        }
    };
}

$ ->
    ws = new WebSocket("ws://localhost:9000/echo")
    ws.onopen = () ->
        ws.send("foo")
    ws.onmessage = (message) ->
        console.log(message.data)