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 		assertEquals("ActionResultの型をチェック", resultClass, actualResult
141 				.getClass());
142 		if (actualResult instanceof Forward) {
143 			assertEquals("パスのチェック", expectedPath, ((Forward) actualResult)
144 					.getPath());
145 		} else if (actualResult instanceof Redirect) {
146 			assertEquals("パスのチェック", expectedPath, ((Redirect) actualResult)
147 					.getPath());
148 		}
149 	}
150 
151 	/**
152 	 * アクションメソッドを実行します。
153 	 * 
154 	 * @param originalPath
155 	 *            オリジナルパス
156 	 * @return アクションメソッドの実行結果。アクションメソッドが見つからなかったり結果がない場合は <code>null</code>
157 	 * @throws Exception
158 	 *             アクションメソッドの実行時に例外が発生した場合
159 	 */
160 	protected ActionResult processAction(final String originalPath)
161 			throws Exception {
162 		final MockHttpServletRequest request = getRequest();
163 		setServletPath(request, originalPath);
164 		final MockHttpServletResponse response = getResponse();
165 		routing(request, response);
166 		setupThreadContext();
167 		final ActionResultWrapper actionResultWrapper = actionProcessor
168 				.process(request, response);
169 		if (actionResultWrapper == null) {
170 			return null;
171 		}
172 		return actionResultWrapper.getActionResult();
173 	}
174 
175 	/**
176 	 * CubbyFilterで行っているルーティングをエミュレートして、内部フォワードパスをリクエストにセットします。
177 	 * 
178 	 * @param request
179 	 *            リクエスト
180 	 * @param response
181 	 *            レスポンス
182 	 * @return 内部フォワードパス
183 	 * @since 1.0.5
184 	 */
185 	protected String routing(final MockHttpServletRequest request,
186 			final MockHttpServletResponse response) {
187 		final InternalForwardInfo internalForwardInfo = router.routing(request,
188 				response);
189 		if (internalForwardInfo == null) {
190 			fail(request.getServletPath() + " could not mapping to action");
191 		}
192 		final String internalForwardPath = internalForwardInfo
193 				.getInternalForwardPath();
194 		final MockHttpServletRequest internalForwardRequest = this
195 				.getServletContext().createRequest(internalForwardPath);
196 		request.setAttribute(ATTR_ROUTINGS, internalForwardInfo
197 				.getOnSubmitRoutings());
198 		request.setAttribute("javax.servlet.forward.request_uri", request
199 				.getRequestURI());
200 		request.setAttribute("javax.servlet.forward.context_path", request
201 				.getContextPath());
202 		request.setAttribute("javax.servlet.forward.servlet_path", request
203 				.getServletPath());
204 		request.setAttribute("javax.servlet.forward.path_info", request
205 				.getPathInfo());
206 		request.setAttribute("javax.servlet.forward.query_string", request
207 				.getQueryString());
208 		final String servletPath = internalForwardRequest.getServletPath();
209 		setServletPath(request, servletPath);
210 		request.setQueryString(internalForwardRequest.getQueryString());
211 		if (StringUtil.isNotBlank(internalForwardRequest.getQueryString())) {
212 			final Map<String, List<String>> pathParameters = parseQueryString(internalForwardRequest
213 					.getQueryString());
214 			for (final Entry<String, List<String>> entry : pathParameters
215 					.entrySet()) {
216 				final String name = entry.getKey();
217 				for (final String value : entry.getValue()) {
218 					request.addParameter(name, value);
219 				}
220 			}
221 		}
222 		return internalForwardPath;
223 	}
224 
225 	/**
226 	 * 指定されたモックリクエストのサーブレットパスを設定します。
227 	 * 
228 	 * @param request
229 	 *            リクエスト
230 	 * @param servletPath
231 	 *            サーブレットパス
232 	 */
233 	private static void setServletPath(final MockHttpServletRequest request,
234 			final String servletPath) {
235 		final Field servletPathField = ClassUtil.getDeclaredField(request
236 				.getClass(), "servletPath");
237 		servletPathField.setAccessible(true);
238 		try {
239 			servletPathField.set(request, servletPath);
240 		} catch (final Exception ex) {
241 			throw new RuntimeException(ex);
242 		}
243 	}
244 
245 	/**
246 	 * {@link ThreadContext}にリクエストをセットします。
247 	 */
248 	protected void setupThreadContext() {
249 		ThreadContext.setRequest(getRequest());
250 	}
251 
252 	/**
253 	 * クエリ文字列をパースして {@link Map} へ変換します。
254 	 * 
255 	 * @param queryString
256 	 *            クエリ文字列
257 	 * @return クエリ文字列をパースした {@link Map}
258 	 */
259 	private Map<String, List<String>> parseQueryString(final String queryString) {
260 		final Map<String, List<String>> params = new HashMap<String, List<String>>();
261 		final String[] tokens = queryString.split("&");
262 		for (final String token : tokens) {
263 			final String[] param = token.split("=");
264 			final String name = param[0];
265 			final String value = param[1];
266 			final List<String> values;
267 			if (params.containsKey(name)) {
268 				values = params.get(name);
269 			} else {
270 				values = new ArrayList<String>();
271 				params.put(name, values);
272 			}
273 			values.add(value);
274 		}
275 		return params;
276 	}
277 
278 	/**
279 	 * CubbyFilterで行っているルーティングをエミュレートして、内部フォワードパスをリクエストにセットします。
280 	 * 
281 	 * @param orginalPath
282 	 *            オリジナルパス
283 	 * @return 内部フォワードパス
284 	 */
285 	@Deprecated
286 	@SuppressWarnings( { "unchecked" })
287 	protected String routing(final String orginalPath) {
288 		final MockHttpServletRequest originalRequest = this.getServletContext()
289 				.createRequest(orginalPath);
290 		final MockHttpServletResponse response = this.getResponse();
291 		final InternalForwardInfo internalForwardInfo = router.routing(
292 				originalRequest, response);
293 		if (internalForwardInfo == null) {
294 			fail(orginalPath + " could not mapping to action");
295 		}
296 		final String internalForwardPath = internalForwardInfo
297 				.getInternalForwardPath();
298 		final MockHttpServletRequest internalForwardRequest = this
299 				.getServletContext().createRequest(internalForwardPath);
300 		final MockHttpServletRequest request = getRequest();
301 		request.setAttribute(ATTR_ROUTINGS, internalForwardInfo
302 				.getOnSubmitRoutings());
303 		Beans.copy(internalForwardRequest, request).execute();
304 		final Field servletPathField = ClassUtil.getDeclaredField(request
305 				.getClass(), "servletPath");
306 		servletPathField.setAccessible(true);
307 		try {
308 			servletPathField.set(request, internalForwardRequest
309 					.getServletPath());
310 		} catch (final Exception ex) {
311 			throw new RuntimeException(ex);
312 		}
313 		request.setQueryString(internalForwardRequest.getQueryString());
314 		if (StringUtil.isNotBlank(internalForwardRequest.getQueryString())) {
315 			request.getParameterMap().putAll(
316 					javax.servlet.http.HttpUtils.parseQueryString(request
317 							.getQueryString()));
318 		}
319 		return internalForwardPath;
320 	}
321 
322 }