Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Kenneth A. Kousen - Making Java Groovy - 2014.pdf
Скачиваний:
50
Добавлен:
19.03.2016
Размер:
15.36 Mб
Скачать

Easy server-side development with groovlets

263

import org.springframework.mock.web.MockHttpServletResponse; import org.springframework.mock.web.MockHttpSession;

class HelloNameServletTest {

HelloNameServlet servlet = new HelloNameServlet()

@Test

void testDoGetWithNoName() {

MockHttpServletRequest request = new MockHttpServletRequest() MockHttpServletResponse response = new MockHttpServletResponse() MockHttpSession session = new MockHttpSession()

request.session = session servlet.doGet(request, response)

assert 'hello.jsp' == response.forwardedUrl

assert request.getAttribute("name") == 'Hello, World' assert session.getAttribute("count") == 1

}

@Test

void testDoGetWithName() {

MockHttpServletRequest request = new MockHttpServletRequest() MockHttpServletResponse response = new MockHttpServletResponse() MockHttpSession session = new MockHttpSession()

request.session = session request.setParameter('name','Dolly') servlet.doGet(request, response)

assert 'hello.jsp' == response.forwardedUrl

assert request.getAttribute("name") == 'Hello, Dolly' assert session.getAttribute("count") == 1

}

}

The ServletCategory isn’t needed in the tests, because I’m already using mock objects rather than the Servlet API classes. Note that the tests check both the request and session attributes and the forwarded URL from the doGet method. The ServletCategory class is a simple example of how to use Groovy’s metaprogramming capabilities to simplify an API.

As a simple alternative to normal servlet development, Groovy provides groovlets.

10.2 Easy server-side development with groovlets

Groovlets are groovy scripts that are executed in response to HTTP requests. A built-in library class called groovy.servlet.GroovyServlet executes them. Like all Groovy scripts, they’re associated with a binding that holds many pre-instantiated variables.

To use a groovlet, first configure the GroovyServlet to receive mapped requests. A typical way of doing so is to add the following XML to the standard web application deployment descriptor, web.xml:

<servlet> <servlet-name>Groovy</servlet-name>

<servlet-class>groovy.servlet.GroovyServlet</servlet-class> </servlet>

<servlet-mapping> <servlet-name>Groovy</servlet-name>

www.it-ebooks.info

264

CHAPTER 10 Building and testing web applications

<url-pattern>*.groovy</url-pattern> </servlet-mapping>

The GroovyServlet class is part of the standard Groovy library. Here it’s mapped to the URL pattern *.groovy, which means that any URL that ends in that pattern will be directed to this servlet. For example, the URL http://localhost/.../hello.groovy would match a script named hello.groovy in the root of the web application. Keep in mind that this is literally the source file, not the compiled class.

GROOVLETS Groovlets are deployed as source code, not compiled.

When invoked, the GroovyServlet class finds the script whose name ends the URL, pre-instantiates a series of variables, creates an instance of the GroovyScriptEngine class, and executes the script. The actual script code can be placed in any accessible directory from the web application root, or in any subdirectory of /WEB-INF/groovy.

The key to the simplicity of groovlets is this already-configured infrastructure. With this in place a developer has a lot less work to do.

10.2.1A “Hello, World!” groovlet

Because every technology needs a “Hello, World!” application, here’s a groovlet to greet the user. Assume that the GroovyServlet has already been configured, and add a file called hello.groovy in the root of a web application. In a standard Maven structure that would be src/main/webapp/hello.groovy. The contents of the groovlet are

name = params.name ?: 'World' println "Hello, $name!"

It’s a simple groovlet, but it should still be tested. Integration-testing of web applications is discussed later in this chapter, but the syntax in the next listing uses the same mechanism for transmitting a GET request (use the Groovy JDK to convert a string to a URL and then call URL’s getText method) that was used in several earlier chapters.

Listing 10.5 HelloGroovletTest, an integration test for the hello groovlet

class HelloGroovletTest { int port = 8163

@Test

void testHelloGroovletWithNoName() { String response =

"http://localhost:$port/HelloGroovlet/hello.groovy"

.toURL().text

assert 'Hello, World!' == response.trim()

}

@Test

void testHelloGroovletWithName() { String response =

"http://localhost:$port/HelloGroovlet/hello.groovy?name=Dolly"

.toURL().text

www.it-ebooks.info

Easy server-side development with groovlets

265

assert 'Hello, Dolly!' == response.trim()

}

}

There’s nothing particularly surprising or unusual about this test, which is simple because the groovlet only responds to GET requests.

Unit tests are also doable, based on the fact that the GroovyServlet is executing the groovlet as a script with predefined variables. For example, the next listing shows a unit test for the groovlet that uses an instance of the GroovyShell class and the Binding class in a manner similar to that described in chapter 6 on testing.

Listing 10.6 A unit test for the groovlet using GroovyShell and Binding

class HelloGroovletUnitTest {

String groovlet = 'src/main/webapp/hello.groovy' GroovyShell shell

Binding binding = new Binding() StringWriter content = new StringWriter()

@Before

Setting the

void setUp() {

params map

binding.params = [:]

 

 

 

 

 

 

binding.out = new PrintWriter(content)

 

 

shell = new GroovyShell(binding)

 

 

}

 

 

 

@Test

void testGroovletWithNoName() { shell.evaluate(new File("$groovlet"))

assert 'Hello, World!' == content.toString().trim()

}

@Test

void testGroovletWithName() { binding.params = [name:'Dolly'] shell.evaluate(new File("$groovlet"))

assert 'Hello, Dolly!' == content.toString().trim()

}

}

Capturing the output stream

The interesting parts of this test are first that the groovlet expects a map of input parameters, so the test has to provide one, and that I need a way to capture the output stream from the groovlet, which is done through the out variable of the binding.

Recall from chapter 6 that Groovy also provides a subclass of GroovyTestCase, called GroovyShellTestCase, which is designed to test scripts like this. The following listing shows the same unit test using GroovyShellTestCase. Note that it’s noticeably simpler.

Listing 10.7 Using GroovyShellTestCase to simplify unit-testing groovlets

class HelloGroovletShellTest extends GroovyShellTestCase { String groovlet = 'src/main/webapp/hello.groovy' StringWriter content = new StringWriter()

def capturedOut = new PrintWriter(content)

www.it-ebooks.info

266

CHAPTER 10 Building and testing web applications

@Test

void testGroovletWithNoName() {

withBinding([out: capturedOut, params:[:]]) { shell.evaluate(new File("$groovlet"))

}

assert 'Hello, World!' == content.toString().trim()

}

@Test

void testGroovletWithName() {

withBinding([out: capturedOut, params:[name:'Dolly']]) { shell.evaluate(new File("$groovlet"))

}

assert 'Hello, Dolly!' == content.toString().trim()

}

}

Pass binding variables through the method

The GroovyShellTestCase class instantiates a GroovyShell internally and allows you to pass a map of binding parameters through the withBinding method.

10.2.2Implicit variables in groovlets

The previous example shows that groovlets expect that all the request parameters are bundled into a map called params. Groovlets operate in an environment containing many implicit variables. Table 10.2 shows the complete list.

Table 10.2 Implicit variables available in groovlets

Variable

Represents

Notes

 

 

 

request

ServletRequest

 

response

ServletResponse

 

session

getSession(false)

May be null

context

ServletContext

 

application

ServletContext (same as context)

 

params

 

Map of request parameters

headers

 

Map of request/response headers

out

response.getWriter()

 

sout

response.getOutputStream()

 

html

new MarkupBuilder(out)

 

 

 

 

The previous example used only the params variable. Now I’ll discuss a slightly more elaborate example, which was used in the Groovy Baseball application first presented in chapter 2. The following listing shows the complete source.

www.it-ebooks.info

Easy server-side development with groovlets

267

 

 

 

 

 

Listing 10.8 The GameService groovlet from the Groovy Baseball application

 

import beans.GameResult;

 

 

 

 

 

import beans.Stadium;

 

 

 

Setting a

 

import service.GetGameData;

 

 

 

 

 

 

 

response

 

 

 

 

 

 

response.contentType = 'text/xml'

 

 

 

header

 

 

 

 

 

 

def month = params.month

 

Access request

 

 

 

def day = params.day

 

 

 

parameters

 

def year = params.year

 

 

 

 

 

 

 

m = month.toInteger() < 10 ? '0' + month : month d = day.toInteger() < 10 ? '0' + day : day

y = year

results = new GetGameData(month:m,day:d,year:y).games

html.games {

 

Writing out

 

XML data

results.each { g ->

 

game(

outcome:"$g.away $g.aScore, $g.home $g.hScore", lat:g.stadium.latitude,

lng:g.stadium.longitude

)

}

}

The goal of the GameService groovlet is to get the date provided by the user interface, invoke the getGames method in the GetGameData service, and provide the results to the user in XML form. The groovlet sets the contentType header in the response to XML, retrieves the input parameters representing the requested date, normalizes them to the proper form if necessary, calls the game service, and uses the built-in markup builder to write out the game results as a block of XML.

Using the markup builder to write out XML is helpful here. One of the problems faced by current web applications is that JavaScript code used in the user interface can’t parse the Java or Groovy objects produced by the server side. An intermediate format is needed that both sides can interpret and generate. There are only two realistic options for that: XML and JavaScript Object Notation (JSON). The recent trend has been to use JSON objects as much as possible, but the markup builder inside groovlets makes it easy to produce XML instead. The amount of XML generated by this application is minimal, so it’s not a problem to parse-in the user interface.

PRODUCING XML Use the html markup builder in groovlets to write out XML when needed, not to produce a web page in HTML.

This demonstration is simple, but that’s the point. Groovlets are a convenient way to receive input data, access back-end services, and produce responses or forward the user to a new destination. Because they have a built-in way to convert objects into XML

www.it-ebooks.info

268

CHAPTER 10 Building and testing web applications

(and it wouldn’t be hard to add a JsonBuilder to convert to JSON instead2), they’re ideal as a front-end for RESTful web services.

Lessons learned (groovlets)

1Groovlets are Groovy scripts executed by an embedded servlet.

2Groovlets contain implicit objects for request parameters, the HTTP session, and more.

3Groovlets use builders to generate formatted output.

Before demonstrating the Grails framework, let me now discuss the issue of testing web applications, both in isolation as unit tests and automated integration tests using Gradle.

10.3 Unitand integration-testing web components

Chapter 6 discussed techniques for unit-testing Java and Groovy classes and demonstrated how Groovy’s mock capabilities provide a standard library of mocks and stubs to support unit tests. It’s easy to test individual classes and to run those tests automatically as part of a build process.

Testing is so important that most modern web frameworks consider testability a major design goal, so they try to make the individual components easy to test. For example, one of the major differences between the original Struts framework and the more modern Struts 2, Spring MVC, JSF, or any of a number of others is how their parts are designed with testing in mind. Despite this, testing of web components is far less pervasive than you might expect.

Still, unit-testing and integration-testing web applications is as important as testing anything else in the system, and doing so automatically is critical. Integration-testing a web application by making a tester manually enter data in forms and click links is an extremely expensive and error-prone mechanism. There has to be a better way, and fortunately Groovy helps a lot in that area.

To lay the foundation, however, I’ll begin with a library of mock classes that comes from one of the biggest Java libraries of them all, the Spring framework.

10.3.1Unit-testing servlets with Spring

The Spring framework is one of the most popular open source libraries in the Java world. Chapter 7 on Groovy and Spring discusses it in some detail, but I want to use it here for two reasons: (1) Spring provides a great collection of mock objects for unittesting web applications, and (2) Spring is one of the underlying technologies for Grails, so knowing more about how Spring works helps you use Grails more effectively.

To illustrate the challenge and highlight the dependencies that need to be mocked during testing, let me start with a simple servlet class, written in Java, called HelloServlet:

2 In fact, I helped do exactly that. That’s open source for you; if you get an idea, go do it.

www.it-ebooks.info

Unitand integration-testing web components

269

public class HelloServlet extends HttpServlet {

protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { resp.getWriter().print("Hello, Servlet!");

}

}

Servlets are all created by inheritance, normally by extending javax.servlet.http

.HttpServlet. HttpServlet is an abstract class with no abstract methods. It receives HTTP requests and delegates them to a do method corresponding to each HTTP verb, like doGet, doPost, doPut, doTrace, or doOptions. Each of these methods takes two arguments, one of type HttpServletRequest and one of type HttpServletResponse.

The HelloServlet class overrides the doGet method to respond to HTTP GET requests. It uses the resp argument (an instance of HttpServletResponse) to get the associated output writer, which is used to print to the output stream.

Even in a class this simple, it’s apparent that unit testing is going to be a challenge. As a reminder of what unit testing is all about, let me say this:

UNIT-TESTING WEB COMPONENTS The goal of unit-testing web applications is to run tests outside of a container. This requires mock objects for all the container-provided classes and services.

In this case I need objects representing the two arguments of type HttpServletRequest and HttpServletResponse. In most cases I’ll also need objects representing

HttpSession, ServletContext, and possibly more.

This is where the set of mock classes from the Spring framework helps. The Spring API includes a package called org.springframework.mock.web that, as described in the API, contains “a comprehensive set of Servlet API 2.53 mock objects, targeted at usage with Spring’s web MVC framework.” Fortunately they can be used with any web application, whether it’s based on Spring MVC or not.

The next listing shows a JUnit test for the doGet method of my “Hello, World!” servlet.

Listing 10.9 HelloServletJavaTest: a servlet test class using mock objects

import static org.junit.Assert.*;

import org.junit.Test;

import org.springframework.mock.web.MockHttpServletRequest; import org.springframework.mock.web.MockHttpServletResponse;

public class HelloServletJavaTest { @Test

public void testDoGet() {

HelloServlet servlet = new HelloServlet(); MockHttpServletRequest req = new MockHttpServletRequest(); MockHttpServletResponse resp = new MockHttpServletResponse();

3 The mock objects work for Servlet 3.0 as well, with some minor exceptions listed in the JavaDocs.

www.it-ebooks.info

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]