View Javadoc

1   /*
2    * Copyright 2004-2008 the Seasar Foundation and the Others.
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *     http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
13   * either express or implied. See the License for the specific language
14   * governing permissions and limitations under the License.
15   */
16  package org.seasar.cubby.unit;
17  
18  import static org.seasar.cubby.CubbyConstants.ATTR_ROUTINGS;
19  
20  import java.lang.reflect.Field;
21  import java.util.ArrayList;
22  import java.util.HashMap;
23  import java.util.List;
24  import java.util.Map;
25  import java.util.Map.Entry;
26  
27  import org.seasar.cubby.action.ActionResult;
28  import org.seasar.cubby.action.Forward;
29  import org.seasar.cubby.action.Redirect;
30  import org.seasar.cubby.controller.ActionProcessor;
31  import org.seasar.cubby.controller.ActionResultWrapper;
32  import org.seasar.cubby.controller.ThreadContext;
33  import org.seasar.cubby.routing.InternalForwardInfo;
34  import org.seasar.cubby.routing.Router;
35  import org.seasar.framework.beans.util.Beans;
36  import org.seasar.framework.mock.servlet.MockHttpServletRequest;
37  import org.seasar.framework.mock.servlet.MockHttpServletResponse;
38  import org.seasar.framework.unit.S2TigerTestCase;
39  import org.seasar.framework.util.ClassUtil;
40  import org.seasar.framework.util.StringUtil;
41  
42  /**
43   * CubbyのActionクラスの単体テスト用のクラスです。
44   * <p>
45   * このクラスを継承して、それぞれのActionクラス用の単体テストを作成します。 詳細はCubbyドキュメントの「アクションのテスト」を参照下さい。
46   * 
47   * <pre>
48   * public class HelloActionTest extends CubbyTestCase {
49   * 	// 対象のアクション
50   * 	private HelloAction action;
51   * 
52   * 	// 初期化処理
53   * 	protected void setUp() throws Exception {
54   * 		// diconファイルの読み込み
55   * 		include(&quot;app.dicon&quot;);
56   * 	}
57   * 
58   * 	public void testIndex() throws Exception {
59   * 		// アクションの実行
60   * 		ActionResult result = processAction(&quot;/hello/&quot;);
61   * 		// 結果のチェック
62   * 		assertPathEquals(Forward.class, &quot;input.jsp&quot;, result);
63   * 	}
64   * 
65   * 	public void setUpMessage() {
66   * 		// リクエストパラメータのセット
67   * 		getRequest().addParameter(&quot;name&quot;, &quot;name1&quot;);
68   * 	}
69   * 
70   * 	public void testMessage() throws Exception {
71   * 		// アクションの実行
72   * 		ActionResult result = processAction(&quot;/hello/message&quot;);
73   * 		// 結果のチェック
74   * 		assertPathEquals(Forward.class, &quot;result.jsp&quot;, result);
75   * 		// 実行後のアクションの状態を確認
76   * 		assertEquals(&quot;name1&quot;, action.name);
77   * 	}
78   * }
79   * </pre>
80   * 
81   * <pre>
82   * public class TodoActionTest extends CubbyTestCase {
83   * 
84   * 	private TodoAction action;
85   * 
86   * 	public void setUpShow() throws Exception {
87   * 		emulateLogin();
88   * 	}
89   * 
90   * 	private void emulateLogin() throws Exception {
91   * 		User user = new User();
92   * 		user.setId(&quot;mock&quot;);
93   * 		user.setName(&quot;mock&quot;);
94   * 		user.setPassword(&quot;mock&quot;);
95   * 		getRequest().getSession().setAttribute(&quot;user&quot;, user);
96   * 	}
97   * 
98   * 	public void testShow() throws Exception {
99   * 		this.readXlsAllReplaceDb(&quot;TodoActionTest_PREPARE.xls&quot;);
100  * 		// CoolURIの場合のテスト
101  * 		ActionResult result = processAction(&quot;/todo/1&quot;);
102  * 		assertPathEquals(Forward.class, &quot;show.jsp&quot;, result);
103  * 		assertEquals(new Integer(1), action.id);
104  * 		assertEquals(&quot;todo1&quot;, action.text);
105  * 		assertEquals(&quot;todo1 memo&quot;, action.memo);
106  * 		assertEquals(new Integer(1), action.todoType.getId());
107  * 		assertEquals(&quot;type1&quot;, action.todoType.getName());
108  * 		assertEquals(&quot;2008-01-01&quot;, action.limitDate);
109  * 	}
110  * }
111  * </pre>
112  * 
113  * </p>
114  * 
115  * @author agata
116  * @author baba
117  * @since 1.0.0
118  */
119 public abstract class CubbyTestCase extends S2TigerTestCase {
120 
121 	/** ルーティング */
122 	private Router router;
123 
124 	/** ActionProcessor */
125 	private ActionProcessor actionProcessor;
126 
127 	/**
128 	 * ActionResultの型とパスをチェックします。
129 	 * 
130 	 * @param resultClass
131 	 *            ActionResultの型
132 	 * @param expectedPath
133 	 *            期待されるパス
134 	 * @param actualResult
135 	 *            チェックするActionResult
136 	 */
137 	public static void assertPathEquals(
138 			final Class<? extends ActionResult> resultClass,
139 			final String expectedPath, final ActionResult actualResult) {
140 		assertPathEquals(resultClass, expectedPath, actualResult, "UTF-8");
141 	}
142 
143 	/**
144 	 * ActionResultの型とパスをチェックします。
145 	 * 
146 	 * @param resultClass
147 	 *            ActionResultの型
148 	 * @param expectedPath
149 	 *            期待されるパス
150 	 * @param actualResult
151 	 *            チェックするActionResult
152 	 * @param characterEncoding
153 	 *            URI のエンコーディング
154 	 */
155 	public static void assertPathEquals(
156 			final Class<? extends ActionResult> resultClass,
157 			final String expectedPath, final ActionResult actualResult,
158 			final String characterEncoding) {
159 		assertEquals("ActionResultの型をチェック", resultClass, actualResult
160 				.getClass());
161 		if (actualResult instanceof Forward) {
162 			assertEquals("パスのチェック", expectedPath, Forward.class.cast(
163 					actualResult).getPath(characterEncoding));
164 		} else if (actualResult instanceof Redirect) {
165 			assertEquals("パスのチェック", expectedPath, Redirect.class.cast(
166 					actualResult).getPath(characterEncoding));
167 		}
168 	}
169 
170 	/**
171 	 * アクションメソッドを実行します。
172 	 * 
173 	 * @param originalPath
174 	 *            オリジナルパス
175 	 * @return アクションメソッドの実行結果。アクションメソッドが見つからなかったり結果がない場合は <code>null</code>
176 	 * @throws Exception
177 	 *             アクションメソッドの実行時に例外が発生した場合
178 	 */
179 	protected ActionResult processAction(final String originalPath)
180 			throws Exception {
181 		final MockHttpServletRequest request = getRequest();
182 		setServletPath(request, originalPath);
183 		final MockHttpServletResponse response = getResponse();
184 		routing(request, response);
185 		setupThreadContext();
186 		final ActionResultWrapper actionResultWrapper = actionProcessor
187 				.process(request, response);
188 		if (actionResultWrapper == null) {
189 			return null;
190 		}
191 		return actionResultWrapper.getActionResult();
192 	}
193 
194 	/**
195 	 * CubbyFilterで行っているルーティングをエミュレートして、内部フォワードパスをリクエストにセットします。
196 	 * 
197 	 * @param request
198 	 *            リクエスト
199 	 * @param response
200 	 *            レスポンス
201 	 * @return 内部フォワードパス
202 	 * @since 1.0.5
203 	 */
204 	protected String routing(final MockHttpServletRequest request,
205 			final MockHttpServletResponse response) {
206 		final InternalForwardInfo internalForwardInfo = router.routing(request,
207 				response);
208 		if (internalForwardInfo == null) {
209 			fail(request.getServletPath() + " could not mapping to action");
210 		}
211 		final String internalForwardPath = internalForwardInfo
212 				.getInternalForwardPath();
213 		final MockHttpServletRequest internalForwardRequest = this
214 				.getServletContext().createRequest(internalForwardPath);
215 		request.setAttribute(ATTR_ROUTINGS, internalForwardInfo
216 				.getOnSubmitRoutings());
217 		request.setAttribute("javax.servlet.forward.request_uri", request
218 				.getRequestURI());
219 		request.setAttribute("javax.servlet.forward.context_path", request
220 				.getContextPath());
221 		request.setAttribute("javax.servlet.forward.servlet_path", request
222 				.getServletPath());
223 		request.setAttribute("javax.servlet.forward.path_info", request
224 				.getPathInfo());
225 		request.setAttribute("javax.servlet.forward.query_string", request
226 				.getQueryString());
227 		final String servletPath = internalForwardRequest.getServletPath();
228 		setServletPath(request, servletPath);
229 		request.setQueryString(internalForwardRequest.getQueryString());
230 		if (StringUtil.isNotBlank(internalForwardRequest.getQueryString())) {
231 			final Map<String, List<String>> pathParameters = parseQueryString(internalForwardRequest
232 					.getQueryString());
233 			for (final Entry<String, List<String>> entry : pathParameters
234 					.entrySet()) {
235 				final String name = entry.getKey();
236 				for (final String value : entry.getValue()) {
237 					request.addParameter(name, value);
238 				}
239 			}
240 		}
241 		return internalForwardPath;
242 	}
243 
244 	/**
245 	 * 指定されたモックリクエストのサーブレットパスを設定します。
246 	 * 
247 	 * @param request
248 	 *            リクエスト
249 	 * @param servletPath
250 	 *            サーブレットパス
251 	 */
252 	private static void setServletPath(final MockHttpServletRequest request,
253 			final String servletPath) {
254 		final Field servletPathField = ClassUtil.getDeclaredField(request
255 				.getClass(), "servletPath");
256 		servletPathField.setAccessible(true);
257 		try {
258 			servletPathField.set(request, servletPath);
259 		} catch (final Exception ex) {
260 			throw new RuntimeException(ex);
261 		}
262 	}
263 
264 	/**
265 	 * {@link ThreadContext}にリクエストをセットします。
266 	 */
267 	protected void setupThreadContext() {
268 		ThreadContext.setRequest(getRequest());
269 	}
270 
271 	/**
272 	 * クエリ文字列をパースして {@link Map} へ変換します。
273 	 * 
274 	 * @param queryString
275 	 *            クエリ文字列
276 	 * @return クエリ文字列をパースした {@link Map}
277 	 */
278 	private Map<String, List<String>> parseQueryString(final String queryString) {
279 		final Map<String, List<String>> params = new HashMap<String, List<String>>();
280 		final String[] tokens = queryString.split("&");
281 		for (final String token : tokens) {
282 			final String[] param = token.split("=");
283 			final String name = param[0];
284 			final String value = param[1];
285 			final List<String> values;
286 			if (params.containsKey(name)) {
287 				values = params.get(name);
288 			} else {
289 				values = new ArrayList<String>();
290 				params.put(name, values);
291 			}
292 			values.add(value);
293 		}
294 		return params;
295 	}
296 
297 	/**
298 	 * CubbyFilterで行っているルーティングをエミュレートして、内部フォワードパスをリクエストにセットします。
299 	 * 
300 	 * @param orginalPath
301 	 *            オリジナルパス
302 	 * @return 内部フォワードパス
303 	 */
304 	@Deprecated
305 	@SuppressWarnings( { "unchecked" })
306 	protected String routing(final String orginalPath) {
307 		final MockHttpServletRequest originalRequest = this.getServletContext()
308 				.createRequest(orginalPath);
309 		final MockHttpServletResponse response = this.getResponse();
310 		final InternalForwardInfo internalForwardInfo = router.routing(
311 				originalRequest, response);
312 		if (internalForwardInfo == null) {
313 			fail(orginalPath + " could not mapping to action");
314 		}
315 		final String internalForwardPath = internalForwardInfo
316 				.getInternalForwardPath();
317 		final MockHttpServletRequest internalForwardRequest = this
318 				.getServletContext().createRequest(internalForwardPath);
319 		final MockHttpServletRequest request = getRequest();
320 		request.setAttribute(ATTR_ROUTINGS, internalForwardInfo
321 				.getOnSubmitRoutings());
322 		Beans.copy(internalForwardRequest, request).execute();
323 		final Field servletPathField = ClassUtil.getDeclaredField(request
324 				.getClass(), "servletPath");
325 		servletPathField.setAccessible(true);
326 		try {
327 			servletPathField.set(request, internalForwardRequest
328 					.getServletPath());
329 		} catch (final Exception ex) {
330 			throw new RuntimeException(ex);
331 		}
332 		request.setQueryString(internalForwardRequest.getQueryString());
333 		if (StringUtil.isNotBlank(internalForwardRequest.getQueryString())) {
334 			request.getParameterMap().putAll(
335 					javax.servlet.http.HttpUtils.parseQueryString(request
336 							.getQueryString()));
337 		}
338 		return internalForwardPath;
339 	}
340 
341 }