Tải bản đầy đủ (.pdf) (50 trang)

Seam Framework Experience the Evolution of Java EE 2nd phần 8 pdf

Bạn đang xem bản rút gọn của tài liệu. Xem và tải ngay bản đầy đủ của tài liệu tại đây (274.11 KB, 50 trang )

ptg
24.4.3 Selecting a Task in the UI
We just saw that web actions are associated with jBPM tasks via the taskId parameter.
Each available task in the waiting state has a
taskId. But how do users determine the
available
taskIds, and how can they assign tasks to themselves or other users? This is
possible via built-in Seam jBPM components.
Business Processes and Conversations
We can draw an analogy here between business processes and long-running conversations.
When a user has multiple long-running conversations, he or she can choose one to join by
switching the browser window or selecting from the
#{conversationList}. Business
processes are not tied to browser windows; the Seam components in this section are the
business process equivalents to
#{conversationList}.
24.4.3.1 The pooledTaskInstanceList Component
The pooledTaskInstanceList component finds all the task instances that can be as-
signed to the logged-in user. This can be used, for example, in a ticketing system where
an admin gets the list of unassigned tasks he or she can work on. This example code
could be used (e.g., on the
assignableTickets.xhtml page):
<h:dataTable value="#{pooledTaskInstanceList}" var="task">
<h:column>
<f:facet name="header">Id</f:facet>
#{task.id}
</h:column>
<h:column>
<f:facet name="header">
Description
</f:facet>


#{task.description}
</h:column>
</h:dataTable>
As we specified in the process definition file (see Section 24.3), the
#{task.description} is the #{ticket.title} in the task’s process scope.
24.4.3.2 The pooledTask Component
This component is typically used inside a #{pooledTaskInstanceList} data table.
It provides a unique method of assigning a task to the current logged-in actor. The
id
of the task to assign must be passed as a request parameter so that the action method
(i.e., the
@BeginTask method) can determine which task it starts for. To use this
CHAPTER 24 MANAGING BUSINESS PROCESSES
328
From the Library of sam kaplan
Simpo PDF Merge and Split Unregistered Version -
ptg
component, you can write the following code; the #{task.id} comes from the
#{pooledTaskInstanceList} iterator (see the previous section):
<h:commandLink action="#{pooledTask.assignToCurrentActor}">
<h:commandButton value="Assign"/>
<f:param name="taskId" value="#{task.id}"/>
</h:commandLink>
24.4.3.3 The taskInstanceList Component
This component’s goal is to get all the task instances that have been assigned to
the logged-in user. In the Ticketing example, this component is used in the
assignedTickets.xhtml page to show a list of processes (i.e., tickets) already assigned
to the user.
<h:dataTable value="#{taskInstanceList}" var="task">
<h:column>

<f:facet name="header">Id</f:facet>
#{task.id}
</h:column>
<h:column>
<f:facet name="header">
Description
</f:facet>
#{task.description}
</h:column>
</h:dataTable>
24.4.3.4 The taskInstanceListByType Component
This component can be seen as a filtered version of the previous component. Instead
of returning the whole list of task instances, this component returns only the task
instances of a certain type.
<h:dataTable value="#{taskInstanceListByType['todo']}" var="task">
<h:column>
<f:facet name="header">Id</f:facet>
#{task.id}
</h:column>
<h:column>
<f:facet name="header">
Description
</f:facet>
#{task.description}
</h:column>
</h:dataTable>
In a nutshell, you can use jBPM to define the process, use Seam stateful session
beans to handle the tasks and transitions in the process, and then use Seam’s built-in
components to tie the process actions to UI elements on the JSF page.
329

24.4 MANAGING TASKS
From the Library of sam kaplan
Simpo PDF Merge and Split Unregistered Version -
ptg
24.5 Business Process-Based Page
Navigation Flow
As we saw in Chapter 3, Seam improves JSF’s pageflow management by introducing
pages.xml. In pages.xml, we can define page parameters, actions, as well as stateful
navigation rules based on the internal state of the application.
With jBPM support, Seam further expands the stateful pageflow management facility
to support actual business processes as pageflows. This is another important aspect of
jBPM integration in Seam. To best illustrate how a business process-based pageflow
works, check out the
numberguess example in the book’s source code bundle. The
application has two processes attached to the
numberGuess.xhtml and confirm.xhtml
pages, respectively.
<pages>
<page view-id="/numberGuess.xhtml">
<begin-conversation join="true" pageflow="numberGuess"/>
</page>
<page view-id="/confirm.xhtml">
<begin-conversation nested="true" pageflow="cheat"/>
</page>
</pages>
The numberGuess.xhtml page displays a form for you to guess a random number
generated by the application. After you enter a guess, the application tells you whether
it is too high or too low and asks you to guess again until you reach the right
number. This is the
numberGuess.xhtml page:

<h:outputText value="Higher!"
rendered="#{numberGuess.randomNumber gt numberGuess.currentGuess}"/>
<h:outputText value="Lower!"
rendered="#{numberGuess.randomNumber lt numberGuess.currentGuess}"/>
<br/>
I'm thinking of a number between
#{numberGuess.smallest} and
#{numberGuess.biggest}. You have
#{numberGuess.remainingGuesses} guesses.
<br/>
Your guess:
<h:inputText value="#{numberGuess.currentGuess}"
id="guess" required="true">
<f:validateLongRange maximum="#{numberGuess.biggest}"
minimum="#{numberGuess.smallest}"/>
</h:inputText>
<h:commandButton value="Guess" action="guess"/>
<s:button value="Cheat" view="/confirm.xhtml"/>
<s:button value="Give up" action="giveup"/>
CHAPTER 24 MANAGING BUSINESS PROCESSES
330
From the Library of sam kaplan
Simpo PDF Merge and Split Unregistered Version -
ptg
The Guess and Give up buttons map to guess and giveup transitions in the business
process associated with the page. The
giveup transition is simple: It just redirects to
the
giveup.xhtml page, where you can click on buttons mapped to yes and no
actions. The guess transition is slightly more complex: Seam first executes the

#{numberGuess.guess} method, which compares the user’s guess to the random
number and saves the current guess. Then the process goes on to the
evaluateGuess
decision node. The #{numberGuess.correctGuess} method compares the current guess
with the random number. If the outcome is
true, the process moves to the win node
and displays the
win.xhtml page.
<pageflow-definition name="numberGuess">
<start-page name="displayGuess" view-id="/numberGuess.xhtml">
<redirect/>
<transition name="guess" to="evaluateGuess">
<action expression="#{numberGuess.guess}"/>
</transition>
<transition name="giveup" to="giveup"/>
</start-page>
<decision name="evaluateGuess"
expression="#{numberGuess.correctGuess}">
<transition name="true" to="win"/>
<transition name="false" to="evaluateRemainingGuesses"/>
</decision>
<decision name="evaluateRemainingGuesses"
expression="#{numberGuess.lastGuess}">
<transition name="true" to="lose"/>
<transition name="false" to="displayGuess"/>
</decision>
<page name="giveup" view-id="/giveup.xhtml">
<redirect/>
<transition name="yes" to="lose"/>
<transition name="no" to="displayGuess"/>

</page>
<page name="win" view-id="/win.xhtml">
<redirect/>
<end-conversation/>
</page>
<page name="lose" view-id="/lose.xhtml">
<redirect/>
<end-conversation/>
</page>
</pageflow-definition>
The following are the #{numberGuess.guess} and #{numberGuess.correctGuess}
methods. With the support of business process, these methods need to contain only
business logic code—they do not need to couple it with the navigation logic.
331
24.5 BUSINESS PROCESS-BASED PAGE NAVIGATION FLOW
From the Library of sam kaplan
Simpo PDF Merge and Split Unregistered Version -
ptg
@Name("numberGuess")
@Scope(ScopeType.CONVERSATION)
public class NumberGuess {

public void guess() {
if (currentGuess > randomNumber) {
biggest = currentGuess - 1;
}
if (currentGuess < randomNumber) {
smallest = currentGuess + 1;
}
guessCount ++;

}
public boolean isCorrectGuess() {
return currentGuess == randomNumber;
}
}
If the user loads the confirm.xhtml page, the cheat process starts. If you click on the
button mapped to the
yes action, the #{numberGuess.cheated} is invoked to mark
you as a cheater, and the process moves on to the
cheat node to display the cheat.xhtml
page:
<pageflow-definition name="cheat">
<start-page name="confirm" view-id="/confirm.xhtml">
<transition name="yes" to="cheat">
<action expression="#{numberGuess.cheated}"/>
</transition>
<transition name="no" to="end"/>
</start-page>
<page name="cheat" view-id="/cheat.xhtml">
<redirect/>
<transition to="end"/>
</page>
<page name="end" view-id="/numberGuess.xhtml">
<redirect/>
<end-conversation/>
</page>
</pageflow-definition>
The Back Button
When navigating using a stateful pageflow model, you have to make sure that the application
decides what is possible. Think about the transitions: If you passed a transition, you cannot

go back unless you make it possible in your pageflow definition. If a user decides to press
the Back button in the browser, that could lead to an inconsistent state. Fortunately, Seam
automatically brings the user back to the page that she should be seeing. This enables you
to make sure that a user will not twice place her $1 million order just because she
accidentally pressed the Back button and submitted it again.
CHAPTER 24 MANAGING BUSINESS PROCESSES
332
From the Library of sam kaplan
Simpo PDF Merge and Split Unregistered Version -
ptg
24.6 jBPM Libraries and Configuration
To use jBPM components, you must bundle the jbpm-x.y.z.jar file in your applica-
tion’s JAR file (i.e., the
app.jar inside the EAR file). We recommend JBPM 3.1.2
or above.
You also must add the following configuration files to the root of your EAR file:
*.jpdl.xml defines the business processes, jbpm.cfg.xml configures the jBPM engine,
and
hibernate.cfg.xml configures the database that stores the process states.
ticketing.ear
|+ ticketProcess.jpdl.xml
|+ hibernate.cfg.xml
|+ jbpm.cfg.xml
|+ app.war
|+ app.jar
| |+ class files
| |+ jbpm-3.1.2.jar
| |+ seam.properties
| |+ META-INF
|+ jboss-seam.jar

|+ el.api.jar
|+ el-ri.jar
|+ META-INF
The jbpm.cfg.xml file overrides the default attributes in the jBPM engine. Most impor-
tantly, you must disable the jBPM transaction manager for persistent data because Seam
now manages database access.
<jbpm-configuration>
<jbpm-context>
<service name="persistence">
<factory>
<bean
class="org.jbpm.persistence.db.DbPersistenceServiceFactory">
<field name="isTransactionEnabled">
<false/>
</field>
</bean>
</factory>
</service>
</jbpm-context>
</jbpm-configuration>
The jBPM engine stores the process state in a database to make the process long-
lived—even after the server reboots. The
hibernate.cfg.xml file configures which
database to store the jBPM state data in and loads jBPM data mapping files to set up
database tables. In this example, we just save the jBPM state data in the embedded
HSQL database at
java:/DefaultDS. Many jBPM mapping files exist; we will not list
all of them here. You can refer to the
hibernate.cfg.xml file in the ticketing project
to find out more.

333
24.6 JBPM LIBRARIES AND CONFIGURATION
From the Library of sam kaplan
Simpo PDF Merge and Split Unregistered Version -
ptg
<hibernate-configuration>
<session-factory>
<property name="dialect">
org.hibernate.dialect.HSQLDialect
</property>
<property name="connection.datasource">
java:/DefaultDS
</property>
<property name="transaction.factory_class">
org.hibernate.transaction.JTATransactionFactory
</property>
<property name="transaction.manager_lookup_class">
org.hibernate.transaction.JBossTransactionManagerLookup
</property>
<property name="transaction.flush_before_completion">
true
</property>
<property name="cache.provider_class">
org.hibernate.cache.HashtableCacheProvider
</property>
<property name="hbm2ddl.auto">update</property>
<mapping resource="org/jbpm/db/hibernate.queries.hbm.xml"/>
<mapping />
</session-factory>
</hibernate-configuration>

In addition, you must tell the Seam runtime where to find the *.jpdl.xml files. You
do this by adding a
core:Jbpm component in the components.xml file:
<components>

<core:Jbpm processDefinitions="ticketProcess.jpdl.xml"/>
</components>
Overall, Seam greatly simplifies the development of business process-driven web appli-
cations. Traditional web developers might find the business process concepts a little
confusing initially. But when you get past the basic syntax, you will find this approach
extremely easy to use and very powerful. Seam lowers the bar for applying business
processes in web applications.
CHAPTER 24 MANAGING BUSINESS PROCESSES
334
From the Library of sam kaplan
Simpo PDF Merge and Split Unregistered Version -
ptg
So far, we have discussed how to integrate the Drools rules engine (Chapters 22
and 23) and the jBPM business process engine (Chapter 24) as separate services into
Seam applications. Business processes and rules are naturally complementary to each
other. At each node of the process, we can fire a set of rules to decide what to do next,
based on the current state of the application. This way, we can express a large chunk
of our business logic in a declarative manner and avoid much of the business logic
coding in Java.
In this chapter, we will reimplement the number guess game from Section 24.5, but
using declarative rules, instead of hardcoded business logic in Java, to manage the flow
of the application. This example is adapted from the Seam official examples.
The game asks you to guess the random number it chooses. Every time you make a
guess, the system tells you whether the guess is too high or too low, and adjusts the
permitted number range for the next guess. You are allowed to make 10 guesses in each

game. If you make a correct guess, the game displays the “you won” page. If you make
10 wrong guesses, the game displays the “you lost” page.
25.1 The Process
From the game description above, the game really only has three states: waiting for the
player to enter a guess; declaring a win; and declaring a loss. After the player inputs a
guess, the application figures out which of the three states it needs to enter next, and
the process repeats itself. Based on that, we have the following business process defined:
25
Integrating Business
Processes and Rules
335
From the Library of sam kaplan
Simpo PDF Merge and Split Unregistered Version -
ptg
<pageflow-definition name="numberGuess">
<start-page name="displayGuess"
view-id="/numberGuess.xhtml">
<redirect/>
<transition name="guess" to="drools"/>
</start-page>
<decision name="drools">
<handler class="org.jboss.seam.drools.DroolsDecisionHandler">
<workingMemoryName>workingMemory</workingMemoryName>
<assertObjects>
<element>#{game}</element>
<element>#{guess}</element>
</assertObjects>
</handler>
<transition to="displayGuess"/>
<transition name="lose" to="lose"/>

<transition name="win" to="win"/>
</decision>
<page name="win" view-id="/win.xhtml">
<end-conversation />
<redirect/>
</page>
<page name="lose" view-id="/lose.xhtml">
<end-conversation />
<redirect/>
</page>
</pageflow-definition>
The business process is started when the user loads the numberGuess.xhtml page and
Seam creates the
game component.
@Name("game")
@Scope(ScopeType.CONVERSATION)
public class Game {
private int biggest;
private int smallest;
private int guessCount;
@Create
@Begin(pageflow="numberGuess")
public void begin() {
guessCount = 0;
biggest = 100;
smallest = 1;
}

}
CHAPTER 25 INTEGRATING BUSINESS PROCESSES AND RULES

336
From the Library of sam kaplan
Simpo PDF Merge and Split Unregistered Version -
ptg
The process starts in the displayGuess state. When the user enters a guess and clicks
on the Guess button, the state transits to
drools. In the drools node, the rules are
applied to determine if the user entered the correct guess. If the guess is incorrect and
the maximum number of tries has not been reached, the application transits back to the
displayGuess state with the numberGuess.xhtml page showing the current range of
allowable guesses. Otherwise, the system transits to the
win or lose state based on the
rules and displays the appropriate web page.
The
workingMemory component used in the drools node is created in components.xml,
as we discussed in the previous chapter.
<components >
<drools:rule-base name="ruleBase"
rule-files="numberguess.drl"/>
<drools:managed-working-memory name="workingMemory"
rule-base="#{ruleBase}"/>
<bpm:jbpm>
<bpm:pageflow-definitions>
<value>pageflow.jpdl.xml</value>
</bpm:pageflow-definitions>
</bpm:jbpm>
</components>
25.2 The Rules
The following rules are applied in the drools node to determine which page to navigate
to next and what information to display on the page:

package org.jboss.seam.example.numberguess
import org.jboss.seam.drools.Decision
global Decision decision
global int randomNumber
global Game game
rule High
when
Guess(guess: value > randomNumber)
then
game.setBiggest(guess-1);
end
rule Low
when
Guess(guess: value < randomNumber)
then
game.setSmallest(guess+1);
end
337
25.2 THE RULES
From the Library of sam kaplan
Simpo PDF Merge and Split Unregistered Version -
ptg
rule Win
when
Guess(value==randomNumber)
then
decision.setOutcome("win");
end
rule Lose
when

Game(guessCount==9)
then
if ( decision.getOutcome()==null )
{
decision.setOutcome("lose");
}
end
rule Increment
salience -10
when
Guess()
then
game.incrementGuessCount();
end
When the rule High is satisfied, the guess is too high. In this case, the rule engine de-
creases the upper limit of the next guess, increases the guess count, and sets the no
decision outcome. The
numberGuess.xhtml page will be displayed next with the new
upper limit and guess count.
When the rule
Win is satisfied, the rule engine sets the decision outcome to win. The
win outcome is automatically set to the transition name of the business process node.
The pageflow then brings the user to the
win.xhtml page.
The key integration points between Drools and the pageflow engine are as follows:
• The
drools node shows that a business process can automatically invoke the rules
engine against a working memory.
• The rule’s outcome is automatically set to the name of the business process transition
to the next state.

25.3 Conclusions
The example application in this chapter shows how to use a business process with the
rules engine in a Seam web application. The Java classes in this example are mostly
simple Java beans which supply data binding for the web forms. All the application
flow and business logic is declaratively expressed in configuration files and handled by
a business process engine and rules engine. This type of declarative programming can
be very powerful when you have fast-changing business logic in your system.
CHAPTER 25 INTEGRATING BUSINESS PROCESSES AND RULES
338
From the Library of sam kaplan
Simpo PDF Merge and Split Unregistered Version -
ptg
Testing has become a crucial component in modern software development processes.
As a POJO framework, Seam was designed from the ground up for easy testability.
Seam goes beyond and above what other POJO frameworks do when it comes to
testing. Seam actually provides its own testing framework based on TestNG, which
makes it easy to write automated, out-of-the-container unit and integration tests for
Seam applications. In the next two chapters, you learn how easy it is to write test cases
for Seam applications. We also explain how to set up a proper testing environment for
out-of-the-container testing.
Part VI
Testing Seam Applications
339
From the Library of sam kaplan
Simpo PDF Merge and Split Unregistered Version -
ptg
This page intentionally left blank
From the Library of sam kaplan
Simpo PDF Merge and Split Unregistered Version -
ptg

The wide adoption of agile software development methods, such as Test-Driven
Development (TDD), has made unit testing a central task for software developers. An
average-sized web project can have hundreds, if not thousands, of unit test cases. Hence,
testability has become a core feature for software frameworks.
Plain Old Java Objects (POJOs) are easy to unit-test. You just instantiate a POJO, using
the standard Java
new keyword, and run its methods in any unit-testing framework.
It is no coincidence that the spread of agile methodologies and POJO-based frame-
works happened at the same time in the last couple years. Seam is a POJO-based
framework designed for easy unit testing.
Enterprise POJOs do not live in isolation. They must interact with other POJOs and
infrastructure services (e.g., a database) to perform their tasks. The standard TDD
and agile practice is to “mock” the service environment in the testing environment—that
is, to duplicate the server APIs without actually running the server. However, the mock
services are often difficult to set up and depend on the testing framework you choose.
To address this challenge, Seam comes with a
SeamTest class that greatly simplifies
the mocking tasks. The
SeamTest facility is based on the popular TestNG framework,
and it mocks all Seam services in your development environment.
In this chapter, we will discuss how to use the
SeamTest class to write TestNG unit
tests. Our test cases are written against the
stateful example application discussed in
Chapter 7. To run the tests, enter the
stateful project folder and run the command ant
test
. The build script runs all tests we have in the test directory and reports the results
in the command console as follows:
26

Unit Testing
341
From the Library of sam kaplan
Simpo PDF Merge and Split Unregistered Version -
ptg
$ant test

[testng] PASSED: simulateBijection
[testng] PASSED: unitTestSayHello2
[testng] PASSED: unitTestSayHello
[testng] PASSED: unitTestStartOver
[testng] ===============================================
[testng] HelloWorld
[testng] Tests run: 4, Failures: 0, Skips: 0
[testng] ===============================================
The test results are also available in HTML format in the build/testout directory
(Figure 26.1).
The test results for the stateful projectFigure 26.1
As we discuss in Appendix B, you can use the stateful project as a template and place
your own test cases in the
test directory. This way, you can reuse all configuration
files, library JARs, and the build script. But for the curious, we explain exactly how the
build script sets up the classpath and configuration files to run the tests in Appendix B.
What Is TestNG?
TestNG is a “next generation” testing framework intended to replace JUnit. It supports
many categories of developer tests, including unit tests, integration tests, end-to-end tests,
etc. Compared with JUnit, TestNG tests are more flexible and easier to write.
CHAPTER 26 UNIT TESTING
342
From the Library of sam kaplan

Simpo PDF Merge and Split Unregistered Version -
ptg
Like Seam, TestNG makes extensive use of Java annotations to simplify the code. That
makes it a natural choice for Seam application developers. More importantly, TestNG
provides superior built-in support for mock objects, which are crucial for a testing
framework. Seam takes advantage of this capability and comes with a custom mock frame-
work in the SeamTest class. We cover the use of the SeamTest class in this and the next
chapters.
In this chapter, we provide a basic introduction to TestNG, to get you started with the
framework. All the examples should be fairly self-explanatory. If you are interested in
learning more about TestNG, visit the TestNG web site, .
26.1 A Simple TestNG Test Case
Let’s start with a simple method in the ManagerAction class to illustrate the key elements
of a TestNG unit test case.
public class ManagerAction implements Manager {
public void startOver () {
person = new Person ();
confirmed = false;
valid = false;
}

}
The following method tests the ManagerAction.startOver() method. It instantiates
a
ManagerAction POJO, runs the startOver() method, and checks that the value
manager.confirmed is indeed set to false. It is extremely simple, but it has all the
basic elements of a unit test.
public class HelloWorldTest extends SeamTest {
@Test
public void unitTestStartOver() throws Exception {

Manager manager = new ManagerAction ();
manager.startOver();
assert !manager.getConfirmed ();
}

}
Notice the @Test annotation on the unitTestStartOver() method. It tells TestNG that
this method is a test case and should be executed by the test runner. The
HelloWorldTest
class inherits from SeamTest, which gives test methods access to mock facilities built
into
SeamTest. We do not use any mock services in this simple test case, but you will
see their usefulness in the next section.
343
26.1 A SIMPLE TESTNG TEST CASE
From the Library of sam kaplan
Simpo PDF Merge and Split Unregistered Version -
ptg
TestNG enables you to have multiple test classes and multiple test run configurations.
In each test run configuration, you can choose to run one or several test classes.
A test configuration is defined in an XML file in the classpath. In the
testing.xml test
configuration file, we tell TestNG that it should run the test cases in the
HelloWorldTest
class:
<suite name="HelloWorld" verbose="2" parallel="false">
<test name="HelloWorld">
<classes>
<class name="HelloWorldTest"/>
</classes>

</test>
</suite>
Now we use TestNG’s built-in Ant task to run the test configuration. With the correct
classpath set up, we just need to pass in the test configuration file. Here is a snippet
from the
stateful project’s build.xml file:
<target name="test" depends="compile">
<taskdef resource="testngtasks" classpathref="lib.classpath"/>

<testng outputdir="${build.testout}">
<jvmarg value="-Xmx800M" />
<jvmarg value="-Djava.awt.headless=true" />
<classpath refid="test.classpath"/>
<xmlfileset dir="${test}" includes="testng.xml"/>
</testng>
</target>
The test results appear on the console, as well as in HTML format in the build/testout
directory, as described above.
26.2 Simulating Dependency Bijection
Dependency bijection (see Chapter 1) is extensively used in Seam applications. Although
bijection is easy for developers, it poses challenges for unit tests. Seam depen-
dency bijection annotations can work directly on private data fields. Without getter/setter
methods (or constructor methods), the test framework does not have access to those
private fields, and therefore cannot wire together POJOs and services for testing. An
example is the
person field in the ManagerAction class; it is annotated with both
@In and @Out, but it does not have getter/setter methods. How can the unit test case in
TestNG manipulate the
ManagerAction.person field?
CHAPTER 26 UNIT TESTING

344
From the Library of sam kaplan
Simpo PDF Merge and Split Unregistered Version -
ptg
@Stateful
@Name("manager")
@Scope (SESSION)
public class ManagerAction implements Manager {
@In @Out
private Person person;

}
This is where the mock facilities in the SeamTest class become useful. The SeamTest
class provides the getField() and setField() methods to simulate bijection and op-
erate directly on Seam component’s private fields. The following example shows how
to use the
getField() and setField() methods. We first inject a Person object and
test whether the injection succeeds. Then we run the
ManagerAction.startOver()
method, which refreshes the person field, and test the result to be outjected. It is
important to cast the
getField() result to the proper object type.
public class HelloWorldTest extends SeamTest {
@Test
public void simulateBijection() throws Exception {
Manager manager = new ManagerAction ();
Person in = new Person ();
in.setName ("Michael Yuan");
// Inject the person component
setField (manager, "person", in);

Person out = (Person) getField(manager, "person");
assert out != null;
assert out.getName().equals("Michael Yuan");
manager.startOver();
out = (Person) getField(manager, "person");
assert out != null;
assert out.getName() == null;
}

}
Accessing Private Fields?
The Java specification does not allow access to private fields from outside the class. How
does
SeamTest do it, then? The SeamTest class runs its own embedded Seam runtime,
which instruments the class bytecode to get around the restriction of the regular JVM.
26.3 Mocking the Database and Transaction
Almost all Seam applications store their data in relational databases. Developers must
unit-test database-related functionality. However, database testing outside the server
345
26.3 MOCKING THE DATABASE AND TRANSACTION
From the Library of sam kaplan
Simpo PDF Merge and Split Unregistered Version -
ptg
container is difficult. You must mock all the persistence-related container services, in-
cluding creating a fully functional EJB3
EntityManager, connecting to an embedded
database, and managing database transactions. The
SeamTest class makes it easy to
mock the database services.
The first thing you need to do is create an

EntityManager. The persistence.xml file
contains information on how to connect to the embedded database. In order to bootstrap
the entity manager in a Java SE test environment, we need to specify a non-JTA data
source for testing (similar to the setup discussed in Chapter 4). So, we have the following
test/persistence.xml file; it is loaded in the classpath when we run the tests but not
packaged in the EAR:
<persistence>
<persistence-unit name="helloworld"
transaction-type="RESOURCE_LOCAL">
<provider>
org.hibernate.ejb.HibernatePersistence
</provider>
<non-jta-data-source>java:/DefaultDS</non-jta-data-source>
<properties>

</properties>
</persistence-unit>
</persistence>
You should first create an EntityManagerFactory by passing the persistence
unit name in the
persistence.xml file to a static factory method. From the
EntityManagerFactory, you can create an EntityManager and then inject it into your
Seam component using the
SeamTest.setField() method discussed in the previous
section.
EntityManagerFactory emf =
Persistence.createEntityManagerFactory("helloworld");
EntityManager em = emf.createEntityManager();
Manager manager = new ManagerAction ();
setField(manager, "em", em);

Persistence Context Name
In a seam-gen project, the persistence unit name defaults to the project name. So, if you
are porting the book’s example applications to a seam-gen project, don’t forget to change
the persistence unit name for the createEntityManagerFactory() method before you
run the tests.
Now you can test any database methods in your Seam POJO. All database operations
are performed against an embedded HSQL database bundled in the test environment.
You do not need to set up this database yourself if you use the project template in the
CHAPTER 26 UNIT TESTING
346
From the Library of sam kaplan
Simpo PDF Merge and Split Unregistered Version -
ptg
book’s source code bundle (see Appendix B). If you write any data into the database,
you must enclose the
EntityManager operations in a transaction, for example:
em.getTransaction().begin();
String outcome = manager.sayHello ();
em.getTransaction().commit();
The following is a complete listing of the unitTestSayHello() test case, which tests
the
ManagerAction.sayHello() method in stateful. It ties together everything we’ve
discussed.
public class HelloWorldTest extends SeamTest {
@Test
public void unitTestSayHello() throws Exception {
Manager manager = new ManagerAction ();
EntityManagerFactory emf =
Persistence.createEntityManagerFactory("helloworld");
EntityManager em = emf.createEntityManager();

setField(manager, "em", em);
Person person = new Person ();
person.setName ("Jacob Orshalick");
setField(manager, "person", person);
setField(manager, "confirmed", false);
em.getTransaction().begin();
manager.sayHello ();
em.getTransaction().commit();
List <Person> fans = manager.getFans();
assert fans!=null;
assert fans.get(fans.size()-1).getName().equals("Jacob Orshalick");
person = (Person) getField (manager, "person");
assert person != null;
assert person.getName() == null;
em.close();
}

}
26.4 Loading the Test Infrastructure
As we discussed in Section 26.1, we define the tests in the test/testng.xml file and
run them in the
testng Ant task. The Java source code for all the test cases is located
in the
test directory.
347
26.4 LOADING THE TEST INFRASTRUCTURE
From the Library of sam kaplan
Simpo PDF Merge and Split Unregistered Version -
ptg
To run the tests, especially the mock database tests (see Section 26.3) and integration

tests (see Chapter 27), the test runner must first load the JBoss Embeddable EJB3 con-
tainer and the Seam runtime. All the Seam configuration files for the application must
be on the classpath (or in
META-INF and WEB-INF directories on the classpath), just as
they would be in a real application server.
Using Seam-gen
Projects that seam-gen generates already have the test infrastructure properly set up. You
just need to put the *Test.xml (i.e., the testng.xml equivalent) files and the test case
source files in the test directory and run ant test. You can use the EntityManager and
other EJB3 services in the test cases.
You can use the same configuration files for testing as for deployment, except
for the
WEB-INF/components.xml and META-INF/persistence.xml files. The test/
components.xml
and test/persistence.xml files are copied to the test classpath
when we run the tests. We have covered the
test/persistence.xml file before. The
change we need to make to
components.xml is the JNDI name pattern. We do not need
the EAR name prefix in the EJB3 bean JNDI name pattern because no EAR file exists
in the tests. This change is not needed if you are testing a Seam POJO application (see
example application
jpa).
<components >
same as deployment
<core:init jndi-pattern="#{ejbName}/local" debug="false"/>
</components>
To load the testing infrastructure, you also need to put the support library JARs and
configuration files on the test classpath. Those files are located in the
$SEAM_HOME/lib,

$SEAM_HOME/lib/test, and $SEAM_HOME/bootstrap directories in the sample code
bundle. You must be careful to exclude JARs and directories that might have duplicate
configuration files in them, such as
components.xml. The following are the relevant
parts of the
build.xml file for running the tests:
<property name="lib" location="${seam.home}/lib" />
<property name="applib" location="lib" />
<path id="lib.classpath">
<fileset dir="${lib}" includes="*.jar"/>
<fileset dir="${applib}" includes="*.jar"/>
</path>
<property name="testlib" location="${seam.home}/lib/test" />
<property name="eejb.conf.dir" value="${seam.home}/bootstrap" />
<property name="resources" location="resources" />
<property name="build.test" location="build/test" />
<property name="build.testout" location="build/testout" />

CHAPTER 26 UNIT TESTING
348
From the Library of sam kaplan
Simpo PDF Merge and Split Unregistered Version -
ptg
<target name="test" depends="compile">
<taskdef resource="testngtasks" classpathref="lib.classpath"/>
<mkdir dir="${build.test}"/>
<javac destdir="${build.test}" debug="true">
<classpath>
<path refid="lib.classpath"/>
<pathelement location="${build.classes}"/>

</classpath>
<src path="${test}"/>
</javac>
<copy todir="${build.test}">
<fileset dir="${build.classes}" includes="**/*.*"/>
<fileset dir="${resources}" includes="**/*.*"/>
</copy>
<! Overwrite the WEB-INF/components.xml >
<copy todir="${build.test}/WEB-INF" overwrite="true">
<fileset dir="${test}" includes="components.xml"/>
</copy>
<! Overwrite the META-INF/persistence.xml >
<copy todir="${build.test}/META-INF" overwrite="true">
<fileset dir="${test}" includes="persistence.xml"/>
</copy>
<path id="test.classpath">
<path path="${build.test}" />
<fileset dir="${testlib}">
<include name="*.jar" />
</fileset>
<fileset dir="${lib}">
<exclude name="jboss-seam-ui.jar" />
<exclude name="jboss-seam-wicket.jar" />
<exclude name="interop/**/*" />
<exclude name="gen/**/*" />
<exclude name="src/**/*" />
</fileset>
<path path="${eejb.conf.dir}" />
</path>
<testng outputdir="${build.testout}">

<jvmarg value="-Xmx800M" />
<jvmarg value="-Djava.awt.headless=true" />
<classpath refid="test.classpath"/>
<xmlfileset dir="${test}" includes="testng.xml"/>
</testng>
</target>
The beauty of this test setup is that the test runner bootstraps the entire runtime environ-
ment for Seam. Thus, you can run not only unit tests, but also integration tests that fully
utilize the JSF EL to simulate real-world web interactions.
349
26.4 LOADING THE TEST INFRASTRUCTURE
From the Library of sam kaplan
Simpo PDF Merge and Split Unregistered Version -
ptg
This page intentionally left blank
From the Library of sam kaplan
Simpo PDF Merge and Split Unregistered Version -
ptg
Unit tests are useful, but they have limitations. By definition, unit tests focus on POJOs
and their methods. All the mock infrastructure was there to make it possible to test those
POJOs in relative isolation. That means we do not get to test whether the POJO correctly
interacts with the framework itself. For instance, how do you test whether an outjected
component has the correct value in Seam runtime context? How do you know that
the JSF interactions and EL expressions have the desired effects? This is where we
need integration testing to test live POJOs inside the Seam and JSF runtime. Unlike the
white box unit tests, the integration tests treat the application from the user’s point
of view.
Integration tests can also be much simpler than unit tests, especially when they involve
database operations and other container services. In integration tests, we test live Seam
components instead of the test-instantiated POJOs in unit test cases. An embedded Seam

runtime started by
SeamTest manages those live Seam components. That embedded
Seam runtime provides the exact same services as the Seam runtime in JBoss AS servers.
You do not need to mock the bijection or manually set up the
EntityManager and
transactions for database access.
If you use the book’s example projects as a template (e.g., the
stateful example) or
use seam-gen to generate your projects, you are ready to go with the integration tests.
Just add your own test cases, as described shortly, to the
test directory and run
ant test. No extra configuration or setup is needed.
351
27
Integration Testing
From the Library of sam kaplan
Simpo PDF Merge and Split Unregistered Version -
ptg
Ins and Outs of Server Container Testing
A simple form of integration testing is to deploy the application in JBoss AS and run the
tests manually through a web browser. But for developers, the key requirement for
easy testability is automation. Developers should be able to run integration tests unattended
and view the results in a nicely formatted report. Ideally, the tests should run directly inside
the development environment (i.e., JDK 5.0 or directly inside an IDE) without starting
any server or browser.
The biggest challenge in testing live Seam components is to simulate the JSF UI inter-
actions. How do you simulate a web request, bind values to Seam components, and
then invoke event handler methods from the test case? Fortunately, the Seam testing
framework has made all this easy. In the next section, we will start from a concrete test
example in

integration.
In a Seam web application, we access Seam components through
#{} EL expressions
in JSF pages. To access those components from TestNG test cases, the Seam test
framework does two things. First, it provides a mechanism to simulate (or “drive”) the
entire JSF interaction lifecycle from the test code. Second, it binds test data to Seam
components via JSF EL expressions or reflective method calls. Let’s check out those
two aspects in our test code.
27.1 Simulating JSF Interactions
In each web request/response cycle, JSF goes through several steps (phases) to process
the request and render the response. Using the
FacesRequest inner classes inside
SeamTest, you can simulate test actions in each JSF phase by overriding the appropriate
methods. The
FacesRequest constructor can take a string argument for the target
view-id of this script, followed by any number of page parameters you define in the
pages.xml. The test runner then just calls those lifecycle methods in the order of JSF
lifecycle phases. The following snippet shows the basic structure of a typical script to
test the submission of a web form.
public class HelloWorldTest extends SeamTest {
@Test
public void testSayHello() throws Exception {
new FacesRequest("/mypage.xhtml") {
@Override
protected void updateModelValues() throws Exception {
// Bind simulated user input data objects to Seam components
}
CHAPTER 27 INTEGRATION TESTING
352
From the Library of sam kaplan

Simpo PDF Merge and Split Unregistered Version -

×