-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathhowto.htm
329 lines (250 loc) · 17.8 KB
/
howto.htm
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<title>StrutsTestCase for JUnit</title>
<LINK REL="stylesheet" TYPE="text/css" HREF="stylesheet.css" TITLE="Default">
</head>
<body>
<div class="header">
<h1>StrutsTestCase for JUnit v2.1.4</h1>
<h3 CLASS="announce">Now supporting Struts 1.2 and 1.3, including <a href="#tiles">Tiles</a> and <a href="#subapp">Sub-Applications!</a></h3>
<p>Questions? Comments? Check out the <a href="http://sourceforge.net/forum/?group_id=39190">user forums</a>.</p>
<a href="http://sourceforge.net/donate/index.php?group_id=39190"><img src="http://images.sourceforge.net/images/project-support.jpg" width="88" height="32" border="0" alt="Support This Project" /> </a>
</div>
<h2>What is it?</h2>
<p>StrutsTestCase for JUnit is an extension of the standard JUnit TestCase class that provides facilities
for testing code based on the Struts framework. StrutsTestCase provides both a <a href="api/servletunit/package-summary.html"/>Mock Object approach</a> and a <a href="http://jakarta.apache.org/cactus">Cactus approach</a>
to actually run the Struts ActionServlet, allowing you to test your Struts code with or without a running servlet engine. Because
StrutsTestCase uses the ActionServlet controller to test your code, you can test not only the implementation
of your Action objects, but also your mappings, form beans, and forwards declarations. And because StrutsTestCase already provides validation methods, it's quick and easy to write unit test cases.</p>
<p>StrutsTestCase is compliant with the Java Servlet 2.2, 2.3, and 2.4 specifications, and supports Struts 1.2/1.3, and Cactus 1.7 and JUnit 3.8.1.
<p><b>Please note</b> that StrutsTestCase is no longer backwards compatible with Struts 1.0. The last release compatible with Struts 1.0 is <a href="http://sourceforge.net/project/showfiles.php?group_id=39190">StrutsTestCase v2.0</a>.
<h2>Where does it live?</h2>
<p>StrutsTestCase for JUnit is hosted at <a href="http://sourceforge.net/projects/strutstestcase/">SourceForge</a>. You can find the latest and greatest release of StrutsTestCase <a href="http://sourceforge.net/project/showfiles.php?group_id=39190">here.</a></p>
<p>Javadocs for all of the StrutsTestCase classes can be found <a href="api/index.html">here</a>, and there is also a <a href="http://sourceforge.net/forum/?group_id=39190">discussion forum</a> and a list of <a href="faq.htm">frequently asked questions</a>.</p>
<h2>Mock Testing vs. In-Container Testing</h2>
<p>There are two popular approaches to testing server-side classes: <b>mock objects</b>, which test classes by simulating the server container, and <b>in-container testing</b>, which tests classes running in the actual server container. StrutsTestCase for JUnit allows you to use either approach, with very minimal impact on your actual unit test code. In fact, because the StrutsTestCase setup and validation methods are exactly the same for both approaches, choosing one approach over the other simply effects which base class you use!</p>
<p>StrutsTestCase for JUnit provides two base classes, both of which are extensions of the standard JUnit TestCase. <a href="api/servletunit/struts/MockStrutsTestCase.html">MockStrutsTestCase</a> uses a set of HttpServlet mock objects to simulate the container environment without requiring a running servlet engine. <a href="api/servletunit/struts/CactusStrutsTestCase.html">CactusStrutsTestCase</a> uses the <a href="http://jakarta.apache.org/cactus">Cactus testing framework</a> to test Struts classes in the actual server container, allowing for a testing environment more in line with the actual deployment environment.</p>
<p><b>Please note</b> that while the following examples use the MockStrutsTestCase approach, you could choose to use the Cactus approach by simply subclassing from CactusStrutsTestCase without changing another line of code!</p>
<h2>How does it work?</h2>
<p><b>Please note</b> that the StrutsTestCase distribution comes with these and other examples, as well as step-by-step instructions for running them -- see
the README file in the examples directory for more details. As well, <a href="api/index.html">there are many more methods</a> in the StrutsTestCase library than are illustrated here -- check out the javadocs for a complete picture of what you can do with StrutsTestCase!</p>
<p>Using the popular cookbook approach, let's consider the following code snippet:</p>
<pre class="code">
public class LoginAction extends Action {
public ActionForward perform(ActionMapping mapping,
ActionForm form,
HttpServletRequest request,
HttpServletResponse response)
{
String username = ((LoginForm) form).getUsername();
String password = ((LoginForm) form).getPassword();
ActionErrors errors = new ActionErrors();
if ((!username.equals("deryl")) || (!password.equals("radar")))
errors.add("password",new ActionError("error.password.mismatch"));
if (!errors.empty()) {
saveErrors(request,errors);
return mapping.findForward("login");
}
// store authentication info on the session
HttpSession session = request.getSession();
session.setAttribute("authentication", username);
// Forward control to the specified success URI
return mapping.findForward("success");
}
</pre>
<p>So, what are we doing here? Well, we receive an ActionForm bean which should contain login information. First, we try to get the username and password information, and then check to see if it is valid. If there is a mismatch in the username or password values, we then create an ActionError message with a key to a message catalogue somewhere, and then try to forward to the login screen so we can log in again. If the username and password match, however, we store some authentication information in the session, and we try to forward to the next page.</p>
<p>There are several things we can test here:
<ul>
<li>Does the LoginForm bean work properly? If we place the appropriate parameters in the request, does this bean get instantiated correctly?
<li>If the username or password doesn't match, do the appropriate errors get saved for display on the next screen? Are we sent back to the login page?
<li>If we supply the correct login information, do we get to the correct page? Are we sure there are no errors reported? Does the proper authentication information get saved in the session?
</ul>
StrutsTestCase gives you the ability to test all of these conditions within the familiar JUnit framework. All of the Struts setup -- which really amounts to starting up the ActionServlet -- is taken care of for you.</p>
<p>So, how do we actually do it? Let's start by creating an empty test case, which we extend from the base StrutsTestCase class:</p>
<pre class="code">
public class TestLoginAction extends MockStrutsTestCase {
<em>public void setUp() { super.setUp(); }
public void tearDown() { super.tearDown(); }</em>
public TestLoginAction(String testName) { super(testName); }
public void testSuccessfulLogin() {}
}
</pre>
<p><b>NOTE:</b> If you choose to override the <tt>setUp()</tt> method, you
<b>must</b> explicitly call <tt>super.setUp()</tt>. This method performs some important initialization routines, and StrutsTestCase will not work if it is not called.</p>
<p>The first thing we need to do is to tell Struts which mapping to use in this test. To do so, we specify a path that is associated with a Struts mapping; this is the same mechanism that the Struts tag library method uses.</p>
<pre class="code">
public class TestLoginAction extends MockStrutsTestCase {
public TestLoginAction(String testName) { super(testName); }
public void testSuccessfulLogin() {
<em>setRequestPathInfo("/login");</em>
}
}
</pre>
<p><b>NOTE:</b> By default, the Struts ActionServlet will look for the file <tt>WEB-INF/struts-config.xml</tt>, so you must place the directory that <i>contains</i> WEB-INF in your CLASSPATH. If you would like to use an alternate configuration file, please see the <a href="api/servletunit/struts/MockStrutsTestCase.html#setConfigFile(java.lang.String)">setConfigFile()</a> method for details on how this file is located.</p>
<p>Next we need to pass form bean properties, which we send via the request object (again, just as Struts does):</p>
<pre class="code">
public class TestLoginAction extends MockStrutsTestCase {
public TestLoginAction(String testName) { super(testName); }
public void testSuccessfulLogin() {
setRequestPathInfo("/login");
<em>addRequestParameter("username","deryl");
addRequestParameter("password","radar");</em>
}
}
</pre>
<p>Finally, we need to get the Action to do its thing, which just involves executing the actionPerform method:</p>
<pre class="code">
public class TestLoginAction extends MockStrutsTestCase {
public TestLoginAction(String testName) { super(testName); }
public void testSuccessfulLogin() {
setRequestPathInfo("/login");
addRequestParameter("username","deryl");
addRequestParameter("password","radar");
<em>actionPerform();</em>
}
}
</pre>
<p>That's all you have to do to get the ActionServlet to process your request, and if all goes well, then nothing will happen. But we're not done yet -- we still need to verify that everything happened as we expected it to. First, we want to make sure we got to the right page:</p>
<pre class="code">
public class TestLoginAction extends MockStrutsTestCase {
public TestLoginAction(String testName) { super(testName); }
public void testSuccessfulLogin() {
setRequestPathInfo("/login");
addRequestParameter("username","deryl");
addRequestParameter("password","radar");
actionPerform();
<em>verifyForward("success");</em>
}
}
</pre>
<p>It's worth noting here that when you verify which page you ended up at, you can use the Struts forward mapping. You don't have to hard code filenames -- the StrutsTestCase framework takes care of this for you. Thus, if you were to change where "success" pointed to, your tests would still work correctly. All in the spirit of Struts.</p>
<p>Next, we want to make sure that authentication information was stored properly:</p>
<pre class="code">
public class TestLoginAction extends MockStrutsTestCase {
public TestLoginAction(String testName) { super(testName); }
public void testSuccessfulLogin() {
setRequestPathInfo("/login");
addRequestParameter("username","deryl");
addRequestParameter("password","radar");
actionPerform();
verifyForward("success");
<em>assertEquals("deryl",(String) getSession().getAttribute("authentication"));</em>
}
}
</pre>
<p>Here we're getting the session object from the request, and checking to see if it has the proper attribute and value.
You could just as easily place an object on the session that your Action object expects to find.
All of the servlet classes available in the StrutsTestCase base classes are fully functioning objects.</p>
<p>Finally, we want to make sure that no ActionError messages were sent along. We can use a built in method
to make sure of this condition: </p>
<pre class="code">
public class TestLoginAction extends MockStrutsTestCase {
public TestLoginAction(String testName) { super(testName); }
public void testSuccessfulLogin() {
setRequestPathInfo("/login");
addRequestParameter("username","deryl");
addRequestParameter("password","radar");
actionPerform();
verifyForward("success");
assertEquals("deryl",(String) getSession().getAttribute("authentication"));
<em>verifyNoActionErrors();</em>
}
}
</pre>
<p>So, now that we've written one test case, it's easy to write another. For example, we'd probably want to test the case where a user supplies incorrect login information. We'd write such a test case like the following:</p>
<pre class="code">
public void testFailedLogin() {
addRequestParameter("username","deryl");
addRequestParameter("password","express");
setRequestPathInfo("/login");
actionPerform();
<em>verifyForward("login");
verifyActionErrors(new String[] {"error.password.mismatch"});</em>
assertNull((String) getSession().getAttribute("authentication"));
}
</pre>
<p>Now, this looks quite similar to our first test case, except that we're passing incorrect
information. Also, we're checking to make sure we used a different forward, namely one
that takes us back to the login page, and that the authentication information is <i>not</i>
on the session.</p>
<p>We're also verifying that the correct error messages
were sent. Note that we used the symbolic name, not the actual text. Because the
<tt>verifyActionErrors()</tt> method takes a String array, we can verify more than
one error message, and StrutsTestCase will make sure there is an exact match. If the test
produced more error messages than we were expecting, it will fail; if it produced fewer, it
will also fail. Only an exact match in name and number will pass.</p>
<p>It's that easy! As you can see, StrutsTestCase not only tests the implementation of your Action objects, but also
the mappings that execute them, the ActionForm beans that are passed as arguments, and the
error messages and forward statements that result from execution. It's the whole enchilada!</p>
<h2 id="tiles">Testing Tiles in Struts 1.2</h2>
<p>The Tiles framework, which is integrated into Struts 1.2, is a flexible templating mechanism designed
to easily re-use common user experience elements. StrutsTestCase now provides support for testing applications
that use Tiles, allowing any test case to verify that an Action object uses the correct Tiles definition. Tiles
testing is similar to calling <tt>verifyActionForward</tt>, with a twist:
<pre class="code">
public class TestLoginAction extends MockStrutsTestCase {
public TestLoginAction(String testName) { super(testName); }
public void testSuccessfulLogin() {
setConfigFile("/WEB-INF/struts-config.xml");
setRequestPathInfo("/login.do");
addRequestParameter("username","deryl");
addRequestParameter("password","radar");
actionPerform();
<em>verifyTilesForward("success","success.tiles.def");</em>
}
}
</pre>
<p>This is similar to our previous test cases, except that we're additionally passing in a definition name
to verify that this Action uses a given Tiles definition when resolving the expected forward. If it uses
a different Tiles definition, or if the expected definition does not exist, then this test will fail. Additionally,
you can call <b><tt>verifyInputTilesForward</tt></b> to verify that an Action uses an input mapping, and that input mapping is the expected Tiles definition:
<pre class="code">
public class TestLoginAction extends MockStrutsTestCase {
public TestLoginAction(String testName) { super(testName); }
public void testSuccessfulLogin() {
setConfigFile("/WEB-INF/struts-config.xml");
setRequestPathInfo("/login.do");
addRequestParameter("username","deryl");
addRequestParameter("password","radar");
actionPerform();
<em>verifyInputTilesForward("success.tiles.def");</em>
}
}
</pre>
<h2 id="subapp">Testing Sub-Applications in Struts 1.2</h2>
<p>Struts 1.2 introduces the concept of sub-applications, or modules -- a powerful mechanism for dividing an
application into functional components. StrutsTestCase now provides support for testing sub-applications,
which extends the concepts discussed in the previous examples. The general idea is still the same: you
can point a unit test to a configuration file, execute an action, and validate the results. The methods
for setting the configuration file and executing an action are a little different, however, as the following
example shows:</p>
<pre class="code">
public class TestLoginAction extends MockStrutsTestCase {
public TestLoginAction(String testName) { super(testName); }
public void testSuccessfulLogin() {
<em>setConfigFile("mymodule","/WEB-INF/struts-config-mymodule.xml");
setRequestPathInfo("/mymodule","/login.do");</em>
addRequestParameter("username","deryl");
addRequestParameter("password","radar");
actionPerform();
verifyForward("success");
assertEquals("deryl",(String) getSession().getAttribute("authentication"));
verifyNoActionErrors();
}
}
</pre>
<p>As you can see, this looks very similar to our other test cases. The first important difference is in setting the configuration
file. Here, we set the files <i>and</i> associate this configuration file with a given sub-application name. This allows the
StrutsTestsCase library to pass this configuration information so that the ActionServlet can correctly identify the sub-application.
Note that the same rules apply to setting the CLASSPATH to locate the configuration file as those mentioned above.</p>
<p>The other important difference is in setting the request path information. Here, we set not only the path information, but also
the sub-application names. The combination of these two parameters is equivalent to the entire request path; in our test case
above, this would be equivalent to a path like this: <b><tt>/mymodule/login.do</tt></b>. It is important to note that when using
this method, the first argument <i>must contain only the sub-application name</i>, and the second argument must contain the rest
of the path <i>not including the sub-application name</i>. Otherwise, the request path will be incorrectly constructed, resulting
in a spurious test failure.</p>
<center><A href="http://sourceforge.net"> <IMG src="http://sourceforge.net/sflogo.php?group_id=39190" width="88" height="31" border="0" alt="SourceForge Logo"></A></center>
<p class="copyright">Copyright (C) 2004 <a href="mailto:deryl.at.acm.org">Deryl Seale</a></p>
</body>
</html>