Building Java Web Applications
Gradle includes a
war plugin for building Java web applications, and the community supplies an excellent plugin called grettyfor 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
grettyplugin 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 grettyplugins {
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.gradlefile under the testCompile configuration.
Adding the Mockito library to
build.gradledependencies {
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