Cubby のテストを S2JUnit4 環境で行う
Cubby のアクションのテストを S2JUnit4 環境で行う方法を試してみました。テストコードはこんな感じになりますです。
@RunWith(Seasar2.class) public class IndexActionTest { public TestContext ctx; public CubbyRunner cubbyRunner; @Test public void index() throws Exception{ ActionResult actualResult = cubbyRunner.run("/"); CubbyAssert.assertPathEquals(Forward.class, "index.jsp", actualResult); } }
Eclipse で Run/Debug 設定などいじらなくても、メソッド単位の実行が出来て良い感じです。(Run/Debug 設定をクラス毎に JUnit3 のランナーに変える以外に方法を知らなかったので。。。)
書いたコード
書いたのは三つ。まずインターフェース。CubbyTestCase#processAction に該当する所。
public interface CubbyRunner { public ActionResult run(String path) throws Exception; }
次に実装クラス。ほぼ CubbyTestCase ですが、request などをメソッド呼び出ししている部分を Injection されるように変更。(元は Cubby 1.1.0 の TestCase)
public class CubbyRunnerImpl implements CubbyRunner { @Binding private Router router; @Binding private ActionProcessor actionProcessor; @Binding private MockHttpServletRequest request; @Binding private MockHttpServletResponse response; @Binding private MockServletContext application; public ActionResult run(String originalPath) throws Exception { final MockHttpServletRequest request = this.request; setServletPath(request, originalPath); final MockHttpServletResponse response = this.response; routing(request, response); setupThreadContext(); final ActionResultWrapper actionResultWrapper = actionProcessor.process(request, response); if (actionResultWrapper == null) { return null; } return actionResultWrapper.getActionResult(); } protected String routing(final MockHttpServletRequest request, final MockHttpServletResponse response) { final InternalForwardInfo internalForwardInfo = router.routing(request, response); if (internalForwardInfo == null) { throw new RuntimeException(request.getServletPath() + " could not mapping to action"); } final String internalForwardPath = internalForwardInfo.getInternalForwardPath(); final MockHttpServletRequest internalForwardRequest = this.application.createRequest(internalForwardPath); request.setAttribute(ATTR_ROUTINGS, internalForwardInfo.getOnSubmitRoutings()); request.setAttribute("javax.servlet.forward.request_uri", request.getRequestURI()); request.setAttribute("javax.servlet.forward.context_path", request.getContextPath()); request.setAttribute("javax.servlet.forward.servlet_path", request.getServletPath()); request.setAttribute("javax.servlet.forward.path_info", request.getPathInfo()); request.setAttribute("javax.servlet.forward.query_string", request.getQueryString()); final String servletPath = internalForwardRequest.getServletPath(); setServletPath(request, servletPath); request.setQueryString(internalForwardRequest.getQueryString()); if (StringUtil.isNotBlank(internalForwardRequest.getQueryString())) { final Map<String, List<String>> pathParameters = parseQueryString(internalForwardRequest.getQueryString()); for (final Entry<String, List<String>> entry : pathParameters.entrySet()) { final String name = entry.getKey(); for (final String value : entry.getValue()) { request.addParameter(name, value); } } } return internalForwardPath; } private static void setServletPath(final MockHttpServletRequest request, final String servletPath) { final Field servletPathField = ClassUtil.getDeclaredField(request.getClass(), "servletPath"); servletPathField.setAccessible(true); try { servletPathField.set(request, servletPath); } catch (final Exception ex) { throw new RuntimeException(ex); } } protected void setupThreadContext() { ThreadContext.setRequest(this.request); } private Map<String, List<String>> parseQueryString(final String queryString) { final Map<String, List<String>> params = new HashMap<String, List<String>>(); final String[] tokens = queryString.split("&"); for (final String token : tokens) { final String[] param = token.split("="); final String name = param[0]; final String value = param[1]; final List<String> values; if (params.containsKey(name)) { values = params.get(name); } else { values = new ArrayList<String>(); params.put(name, values); } values.add(value); } return params; } }
CubbyRunner に入れてもよいのですが、何となく流儀にしたがって CubbyAssert 。
public class CubbyAssert extends S2Assert { public static void assertPathEquals( final Class<? extends ActionResult> resultClass, final String expectedPath, final ActionResult actualResult) { assertEquals("ActionResultの型をチェック", resultClass, actualResult.getClass()); if (actualResult instanceof Forward) { assertEquals("パスのチェック", expectedPath, ((Forward) actualResult).getPath()); } else if (actualResult instanceof Redirect) { assertEquals("パスのチェック", expectedPath, ((Redirect) actualResult).getPath()); } } }
設定周り
やったのは、s2junit4-cubby.dicon の作成と s2junit4.dicon への設定の追加。
まず、s2junit4-cubby.dicon 。CubbyRunner のコンポーネント設定と、Router と ActionProcessor を Injection するため、cubby.dicon と routing.dicon を include 。
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE components PUBLIC "-//SEASAR//DTD S2Container 2.4//EN" "http://www.seasar.org/dtd/components24.dtd"> <components namespace="s2junit4-cubby"> <include path="cubby.dicon" /> <include path="routing.dicon" /> <component class="unit.CubbyRunnerImpl" /> </components>
s2junit4.dicon は ここ のを少しカスタマイズ。ConfigFileIncluderImpl に app.dicon と上記の s2juni4-cubby.dicon を追加。
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE components PUBLIC "-//SEASAR//DTD S2Container 2.4//EN" "http://www.seasar.org/dtd/components24.dtd"> <components namespace="s2junit4"> <component name="context" class="org.seasar.framework.unit.impl.InternalTestContextImpl"> <property name="ejb3Enabled">false</property> </component> <component class="org.seasar.framework.unit.impl.DataAccessorImpl" /> <component class="org.seasar.framework.unit.impl.ConfigFileIncluderImpl"> <initMethod name="addConfigFile"> <arg>"app.dicon"</arg> </initMethod> <initMethod name="addConfigFile"> <arg>"s2junit4-cubby.dicon"</arg> </initMethod> <initMethod name="addConfigFile"> <arg>context.testClassShortName + ".dicon"</arg> </initMethod> </component> :
実行コードはほとんど変えず、クラスの乗せ換えと dicon の設定だけでいけました。ぼつぼつこっちに置き換えてみて問題ないか確かめよう。