Building Java Web Applications
Gradle includes a
war
plugin for building Java web applications, and the community supplies an excellent plugin called gretty
for testing and deploying web applications on Jetty or Tomcat. This guide demonstrates how to build a simple web app and deploy it on Jetty using the gretty
plugin. You’ll also learn how to write a unit test for a servlet using the Mockito framework and how to write a functional test for the web app using gretty
and Selenium.
Contents
- What you’ll need
- Create the structure of a web application
- Add a Gradle build file
- Add a servlet and metadata to the project
- Add JSP pages to the demo application
- Add the
gretty
plugin and run the app - Unit test the servlet using Mockito
- Add a functional test
- Run the functional test
- Summary
- Next steps
- Help improve this guide
What you’ll need
- About 21 minutes
- A text editor or IDE
- A Java distribution, version 7 or higher
- A Gradle distribution, version 4.6 or better
Create the structure of a web application
Gradle includes a
war
plugin that is documented in the Web Application Quickstart and the WAR plugin chapter of the user manual. The war
plugin extends the Java plugin to add support for web applications. By default, it uses a folder called src/main/webapp
for web-related resources.The "Web Application Quickstart" section in the User Manual still refers to the jetty plugin, which is deprecated in favor of the gretty plugin used here. The parts specific to the war plugin are fine, however, and the section will be updated soon. |
Therefore, create the following file structure for a project called
webdemo
:
Sample project layout
webdemo/ src/ main/ java/ webapp/ test java/
Any servlets or other Java classes will go in
src/main/java
, tests will go in src/test/java
, and other web artifacts will go in src/main/webapp
.Add a Gradle build file
Add a file called
build.gradle
to the root of the project, with the following contents:
build.gradle
plugins {
id 'war'
}
repositories {
jcenter()
}
dependencies {
providedCompile 'javax.servlet:javax.servlet-api:3.1.0'
testCompile 'junit:junit:4.12'
}
Using the war plugin | |
Current release version of the servlet API |
The
war
plugin adds the configurations providedCompile
and providedRuntime
, analogous to compile
and runtime
in regular Java applications, to represent dependencies that are needed locally but should not be added to the generated webdemo.war
file.
The
plugins
syntax is used to apply the java
and war
plugins. No version is needed for either, since they are included with the Gradle distribution.
It is a good practice to generate a Gradle wrapper for the project by executing the
wrapper
task:$ gradle wrapper --gradle-version=4.6 :wrapper
This will produce
gradlew
and gradlew.bat
scripts and the gradle
folder with the wrapper jar inside as described in the wrapper section of the User Manual.If you are using Gradle 4.0 or later you may see less output from the console that you might see in this guide. In this guide, output is shown using the --console=plain flag on the command-line. This is done to show the tasks that Gradle is executing. |
Add a servlet and metadata to the project
There are two options for defining web application metadata. Prior to version 3.0 of the servlet specification, metadata resided in a deployment descriptor called
web.xml
in the WEB-INF
folder of the project. Since 3.0, the metadata can be defined using annotations.
Create a package folder
org/gradle/demo
below the src/main/java
folder. Add a servlet file HelloServlet.java
, with the following contents:
src/main/java/org/gradle/demo/HelloServlet.java
package org.gradle.demo;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@WebServlet(name = "HelloServlet", urlPatterns = {"hello"}, loadOnStartup = 1)
public class HelloServlet extends HttpServlet {
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
response.getWriter().print("Hello, World!");
}
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
String name = request.getParameter("name");
if (name == null) name = "World";
request.setAttribute("user", name);
request.getRequestDispatcher("response.jsp").forward(request, response);
}
}
Annotation-based servlet | |
GET request returns a simple string | |
POST request forwards to a JSP page |
The servlet uses the
@WebServlet
annotation for configuration. The doGet
method responds to HTTP GET requests by writing a "Hello, World!" string to the output writer. It reacts to HTTP POST requests by looking for a request parameter called name
and adding it to the request
as an attribute called user
, then forwarding to a response.jsp
page.The war plugin supports the use of the older web.xml deployment descriptor, which by default should reside in the WEB-INF folder under src/main/webapp . Feel free to use that as an alternative to the annotation-based approach. |
You now have a simple servlet that responds to HTTP GET and POST requests.
Add JSP pages to the demo application
Add an index page to the root of the application by creating the file
index.html
in the src/main/webapp
folder, with the following contents:
src/main/webapp/index.html
<html>
<head>
<title>Web Demo</title>
</head>
<body>
<p>Say <a href="hello">Hello</a></p>
<form method="post" action="hello">
<h2>Name:</h2>
<input type="text" id="say-hello-text-input" name="name" />
<input type="submit" id="say-hello-button" value="Say Hello" />
</form>
</body>
</html>
Link submits GET request | |
Form uses POST request |
The
index.html
page uses a link to submit an HTTP GET request to the servlet, and a form to submit an HTTP POST request. The form contains a text field called name
, which is accessed by the servlet in its doPost
method.
In its
doPost
method, the servlet forwards control to another JSP page called response.jsp
. Therefore define a file of that name inside src/main/webapp
with the following contents:
src/main/webapp/response.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Hello Page</title>
</head>
<body>
<h2>Hello, ${user}!</h2>
</body>
</html>
The
response
page accessed the user
variable from the request and renders it inside an h2
tag.
Add the gretty
plugin and run the app
The
gretty
plugin is an outstanding community-supported plugin that can be found in the Gradle plugin repository at https://plugins.gradle.org/plugin/org.akhikhl.gretty
. The plugin makes it easy to run or test webapps on either Jetty or Tomcat.
Add it to our project by adding the following line to the
plugins
block inside build.gradle
.
Updating
build.gradle
to add gretty
plugins {
id 'war'
id 'org.akhikhl.gretty' version '1.4.2'
}
Adding the gretty plugin |
The
gretty
plugin adds a large number of tasks to the application, useful for running or testing in Jetty or Tomcat environments. Now you can build and deploy the app to the default (Jetty) container by using the appRun
task.
Executing the
appRun
task$ ./gradlew appRun :prepareInplaceWebAppFolder :createInplaceWebAppFolder UP-TO-DATE :compileJava :processResources UP-TO-DATE :classes :prepareInplaceWebAppClasses :prepareInplaceWebApp :appRun 12:25:13 INFO Jetty 9.2.15.v20160210 started and listening on port 8080 12:25:13 INFO webdemo runs at: 12:25:13 INFO http://localhost:8080/webdemo Press any key to stop the server. > Building 87% > :appRun BUILD SUCCESSFUL
You can now access the web app at http://localhost:8080/webdemo and either click on the link to execute a GET request or submit the form to execute a POST request.
Although the output says
Press any key to stop the server
, standard input is not intercepted by Gradle. To stop the process, press ctrl-C
.Unit test the servlet using Mockito
The open source Mockito framework makes it easy to unit test Java applications. Add the Mockito dependency to the
build.gradle
file under the testCompile
configuration.
Adding the Mockito library to
build.gradle
dependencies {
providedCompile 'javax.servlet:javax.servlet-api:3.1.0'
testCompile 'junit:junit:4.12'
testCompile 'org.mockito:mockito-core:2.7.19'
}
Adding Mockito |
To unit test the servlet, create a package folder
org.gradle.demo
beneath src/test/java
. Add a test class file HelloServletTest.java
with the following contents:
src/test/java/org/gradle/demo/HelloServletTest.java
package org.gradle.demo;
import org.junit.Before;
import org.junit.Test;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import javax.servlet.RequestDispatcher;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.PrintWriter;
import java.io.StringWriter;
import static org.junit.Assert.assertEquals;
import static org.mockito.Mockito.*;
public class HelloServletTest {
@Mock private HttpServletRequest request;
@Mock private HttpServletResponse response;
@Mock private RequestDispatcher requestDispatcher;
@Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
}
@Test
public void doGet() throws Exception {
StringWriter stringWriter = new StringWriter();
PrintWriter printWriter = new PrintWriter(stringWriter);
when(response.getWriter()).thenReturn(printWriter);
new HelloServlet().doGet(request, response);
assertEquals("Hello, World!", stringWriter.toString());
}
@Test
public void doPostWithoutName() throws Exception {
when(request.getRequestDispatcher("response.jsp"))
.thenReturn(requestDispatcher);
new HelloServlet().doPost(request, response);
verify(request).setAttribute("user", "World");
verify(requestDispatcher).forward(request,response);
}
@Test
public void doPostWithName() throws Exception {
when(request.getParameter("name")).thenReturn("Dolly");
when(request.getRequestDispatcher("response.jsp"))
.thenReturn(requestDispatcher);
new HelloServlet().doPost(request, response);
verify(request).setAttribute("user", "Dolly");
verify(requestDispatcher).forward(request,response);
}
}
The test creates mock objects for the
HttpServletRequest
, HttpServletResponse
, and RequestDispatcher
classes. For the doGet
test, a PrintWriter
that uses a StringWriter
is created, and the mock request object is configured to return it when the getWriter
method is invoked. After calling the doGet
method, the test checks that the returned string is correct.
For the post requests, the mock request is configured to return a given name if present or null otherwise, and the
getRequestDispatcher
method returns the associated mock object. Calling the doPost
method executes the request. Mockito then verifies that the setAttribute
method was invoked on the mock response with the proper arguments and that the forward
method was called on the request dispatcher.
You can now test the servlet using Gradle with the
test
task (or any task, like build
, that depends on it).$ ./gradlew build :compileJava UP-TO-DATE :processResources UP-TO-DATE :classes UP-TO-DATE :war :assemble :compileTestJava :processTestResources UP-TO-DATE :testClasses :test :check :build BUILD SUCCESSFUL
The test output can be accessed from
build/reports/tests/test/index.html
in the usual manner. You should get a result similar to:Add a functional test
The
gretty
plugin combines with Gradle to make it easy to add functional tests to web applications. To do so, add the following lines to your build.gradle
file:
Gretty additions to
build.gradle
for functional testinggretty {
integrationTestTask = 'test'
}
// ... rest from before ...
dependencies {
providedCompile 'javax.servlet:javax.servlet-api:3.1.0'
testCompile 'junit:junit:4.12'
testCompile 'org.mockito:mockito-core:2.7.19'
testCompile 'io.github.bonigarcia:webdrivermanager:1.6.1'
testCompile 'org.seleniumhq.selenium:selenium-java:3.3.1'
}
Tell gretty to start and stop the server on test | |
Automatically installs browser drivers | |
Uses Selenium for functional tests |
The
gretty
plugin needs to know which task requires a start and stop of the server. Frequently that is assigned to your own task, but to keep things simple just use the existing test
task.
Selenium is a popular open-source API for writing functional tests. Version 2.0 is based on the WebDriver API. Recent versions require testers to download and install a version of WebDriver for their browser, which can be tedious and hard to automate. The WebDriverManager project makes it easy to let Gradle handle that process for you.
Add the following functional test to your project, in the
src/test/java
directory:
src/test/java/org/gradle/demo/HelloServletFunctionalTest.java
package org.gradle.demo;
import io.github.bonigarcia.wdm.ChromeDriverManager;
import org.junit.After;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.chrome.ChromeDriver;
import static org.junit.Assert.assertEquals;
public class HelloServletFunctionalTest {
private WebDriver driver;
@BeforeClass
public static void setupClass() {
ChromeDriverManager.getInstance().setup();
}
@Before
public void setUp() {
driver = new ChromeDriver();
}
@After
public void tearDown() {
if (driver != null)
driver.quit();
}
@Test
public void sayHello() throws Exception {
driver.get("http://localhost:8080/webdemo");
driver.findElement(By.id("say-hello-text-input")).sendKeys("Dolly");
driver.findElement(By.id("say-hello-button")).click();
assertEquals("Hello Page", driver.getTitle());
assertEquals("Hello, Dolly!", driver.findElement(By.tagName("h2")).getText());
}
}
Downloads and installs browser driver, if necessary | |
Start the browser automation | |
Shut down the browser when done | |
Run the functional test using the Selenium API |
The WebDriverManager portion of this test checks for the latest version of the binary, and downloads and installs it when it is not present. Then the
sayHello
test method drives a Chrome browser to the root of our application, fills in the input text field, clicks the button, and verifies the title of the destination page and that the h2
tag contains the expected string.
The WebDriverManager system supports Chrome, Opera, Internet Explorer, Microsoft Edge, PhantomJS, and Firefox. Check the project documentation for more details.
Run the functional test
Run the test using the
test
task:$ ./gradlew test :prepareInplaceWebAppFolder UP-TO-DATE :createInplaceWebAppFolder UP-TO-DATE :compileJava UP-TO-DATE :processResources UP-TO-DATE :classes UP-TO-DATE :prepareInplaceWebAppClasses UP-TO-DATE :prepareInplaceWebApp UP-TO-DATE :compileTestJava UP-TO-DATE :processTestResources UP-TO-DATE :testClasses UP-TO-DATE :appBeforeIntegrationTest 12:57:56 INFO Jetty 9.2.15.v20160210 started and listening on port 8080 12:57:56 INFO webdemo runs at: 12:57:56 INFO http://localhost:8080/webdemo :test :appAfterIntegrationTest Server stopped. BUILD SUCCESSFUL
The
gretty
plugin starts up an embedded version of Jetty 9 on the default port, executes the tests, and shuts down the server. If you watch, you’ll see the Selenium system open a new browser, access the site, complete the form, click the button, check the new page, and finally shut down the browser.
Integration tests are often handled by creating a separate source set and dedicated tasks, but that is beyond the scope of this guide. See the Gretty documentation for details.
No comments:
Post a Comment