package org.seasar.ymir.testing; import static org.seasar.ymir.Ymir.ATTR_REQUEST; import static org.seasar.ymir.Ymir.ATTR_RESPONSE; import java.io.File; import java.io.IOException; import java.io.UnsupportedEncodingException; import java.net.URL; import java.util.ArrayList; import java.util.Arrays; import java.util.LinkedHashMap; import java.util.List; import java.util.Locale; import java.util.Map; import javax.servlet.FilterChain; import javax.servlet.RequestDispatcher; import javax.servlet.ServletContext; import javax.servlet.ServletContextEvent; import javax.servlet.ServletException; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.transaction.TransactionManager; import junit.framework.TestCase; import org.seasar.cms.pluggable.Configuration; import org.seasar.cms.pluggable.impl.ConfigurationImpl; import org.seasar.framework.container.S2Container; import org.seasar.framework.container.factory.SingletonS2ContainerFactory; import org.seasar.kvasir.util.ClassUtils; import org.seasar.kvasir.util.io.impl.FileResource; import org.seasar.ymir.Dispatcher; import org.seasar.ymir.FormFile; import org.seasar.ymir.HttpMethod; import org.seasar.ymir.Path; import org.seasar.ymir.Request; import org.seasar.ymir.RequestProcessor; import org.seasar.ymir.Response; import org.seasar.ymir.Ymir; import org.seasar.ymir.YmirContext; import org.seasar.ymir.id.action.Action; import org.seasar.ymir.id.action.GetAction; import org.seasar.ymir.message.Notes; import org.seasar.ymir.mock.servlet.MockFilterChain; import org.seasar.ymir.mock.servlet.MockFilterChainImpl; import org.seasar.ymir.mock.servlet.MockHttpServletRequest; import org.seasar.ymir.mock.servlet.MockHttpServletRequestImpl; import org.seasar.ymir.mock.servlet.MockHttpServletResponse; import org.seasar.ymir.mock.servlet.MockHttpServletResponseImpl; import org.seasar.ymir.mock.servlet.MockHttpSession; import org.seasar.ymir.mock.servlet.MockRequestDispatcherImpl; import org.seasar.ymir.mock.servlet.MockServletContext; import org.seasar.ymir.mock.servlet.MockServletContextImpl; import org.seasar.ymir.mock.servlet.RequestDispatcherFactory; import org.seasar.ymir.servlet.YmirListener; import org.seasar.ymir.util.ContainerUtils; import org.seasar.ymir.util.ServletUtils; /** * YmirのPageクラスをテストするためのTestCaseのベースとなるクラスです。 *
Pageクラスをテストする場合はこのクラスのサブクラスとしてTestCaseを作成すると便利かもしれません。 */ abstract public class YmirTestCase extends TestCase { private static final String DEFAULT_WEBAPPROOT = "src/main/webapp"; private static final Object[] EMPTY_PARAMS = new Object[0]; private String characterEncoding_ = "UTF-8"; private String contextPath_ = "/context"; private Locale locale_ = new Locale(""); private String webappRoot_ = DEFAULT_WEBAPPROOT; private String[] additionalConfigPaths_; private S2Container container_; private YmirListener ymirListener_; private MockServletContext servletContext_; private Ymir ymir_; private MockHttpServletRequest httpRequest_; private MockHttpServletResponse httpResponse_; /** * テストの実行環境を設定します。 *
テストの実行環境を変更したい場合は、このメソッドをオーバライドして変更のための処理を記述して下さい。 *
*/ public void setUpEnvironment() { } /** * リクエストの文字エンコーディングを返します。 *デフォルトでは、返される文字エンコーディングはUTF-8です。 *
* * @return 文字エンコーディング。 */ public String getCharacterEncoding() { return characterEncoding_; } /** * リクエストの文字エンコーディングを設定します。 *このメソッドは{@link #setUpEnvironment()}の中で呼び出して下さい。 *
* * @param characterEncoding 文字エンコーディング。 */ public void setCharacterEncoding(String characterEncoding) { characterEncoding_ = characterEncoding; } /** * テスト時のコンテキストパスを返します。 * * @return コンテキストパス。 */ public String getContextPath() { return contextPath_; } /** * テスト時のコンテキストパスを設定します。 *このメソッドは{@link #setUpEnvironment()}の中で呼び出して下さい。 *
* * @param contextPath コンテキストパス。 */ public void setContextPath(String contextPath) { contextPath_ = contextPath; } /** * リクエストを処理するためのロケールを返します。 *デフォルトでは、返されるロケールは空のロケールです。 *
* * @return ロケール。 */ public Locale getLocale() { return locale_; } /** * リクエストを処理するためのロケールを設定します。 *このメソッドは{@link #setUpEnvironment()}の中で呼び出して下さい。 *
* * @param locale ロケール。 */ public void setLocale(Locale locale) { locale_ = locale; } /** * プロジェクトを開発時にWebアプリケーションサーバにデプロイする際のルートディレクトリを設定します。 *デフォルトは「src/main/webapp」です。 *
*このメソッドは{@link #setUpEnvironment()}の中で呼び出して下さい。 *
* * @param webappRoot Webアプリケーションとして動作させるためのルートディレクトリ。 */ public void setWebappRoot(String webappRoot) { webappRoot_ = webappRoot; } /** * テスト時に追加で読み込ませたい設定ファイルのパスを返します。 * * @return 設定ファイルのパスの配列。 */ public String[] getAdditionalConfigPaths() { return additionalConfigPaths_; } /** * テスト時に追加で読み込ませたい設定ファイルのパスを設定します。 *このメソッドは{@link #setUpEnvironment()}の中で呼び出して下さい。 *
* * @param additionalConfigPaths 設定ファイルのパスの配列。 */ public void setAdditionalConfigPaths(String... additionalConfigPaths) { additionalConfigPaths_ = additionalConfigPaths; } /** * テストのための設定を追加します。 *テストのための設定を追加したい場合はこのメソッドをオーバライドして下さい。 *
* * @param configuration Configurationオブジェクト。 */ public void setUpConfiguration(Configuration configuration) { } /** * 主に単一画面のテスト用に、一時的に@Beginアノテーションのチェックを無効にします。 *conversationに参加している画面をテストする場合、その画面に@Beginがないと * テスト時に不正遷移エラーとみなされてしまいます。 * これを避けるためにこのメソッドを呼び出して一時的に@Beginアノテーションのチェックを無効にして下さい。 * このメソッドが呼び出されてから呼び出したテストメソッドが終了するまで@Beginアノテーションは無効になります。 *
*このメソッドは{@link #setUpConfiguration(Configuration)}の中で呼び出して下さい。 *
*/ public void disableBeginCheck(Configuration configuration) { configuration .setProperty( org.seasar.ymir.conversation.Globals.APPKEY_CORE_CONVERSATION_DISABLEBEGINCHECK, String.valueOf(true)); } /** * 主に単一画面のテスト用に、ConversationScopeの代わりにSessionScopeを使用するようにします。 *conversationに参加している画面をテストする場合、その画面がconversationの途中だと * テスト時にconversation scopeが存在せずオブジェクトの保存や取り出しがうまくいかないことがあります。 * これを避けるためにこのメソッドを呼び出して一時的にconversation scopeの代わりにsession scopeを使うようにして下さい。 * このメソッドが呼び出されてから呼び出したテストメソッドが終了するまでは、 * conversation scopeへのアクセスは全てsession scopeにスルーされるようになります。 *
*このメソッドは{@link #setUpConfiguration(Configuration)}の中で呼び出して下さい。 *
*/ public void useSessionScopeAsConversationScope(Configuration configuration) { configuration .setProperty( org.seasar.ymir.conversation.Globals.APPKEY_CORE_CONVERSATION_USESESSIONSCOPEASCONVERSATIONSCOPE, String.valueOf(true)); } @Override public void runBare() throws Throwable { setUpYmir(); try { setUp(); try { runTest(); } finally { tearDown(); } } finally { tearDownYmir(); } } @Override protected void runTest() throws Throwable { TransactionManager tm = null; if (needTransaction()) { try { tm = getComponent(TransactionManager.class); tm.begin(); } catch (Throwable t) { System.err.println(t); } } try { super.runTest(); } finally { if (tm != null) { tm.rollback(); } } } protected boolean needTransaction() { return getName().endsWith("Tx"); } /** * テストメソッドを呼び出す前の処理を行ないます。 */ protected void setUpYmir() { servletContext_ = new MockServletContextImpl(getContextPath()); servletContext_ .setRequestDispatcherFactory(new InternalRequestDispatcherFactory()); URL resource = getClass().getClassLoader() .getResource("app.properties"); if (resource == null) { throw new RuntimeException("'app.properties' not found."); } servletContext_.setRoot(new FileResource(findWebappRoot(ClassUtils .getFileOfResource(resource).getParentFile()))); servletContext_.setInitParameter(YmirListener.CONFIG_PATH_KEY, "ymir.dicon"); ymirListener_ = new YmirListener() { @Override public void preInit(ServletContextEvent sce) { ConfigurationImpl configuration = (ConfigurationImpl) SingletonS2ContainerFactory .getContainer().getComponent(ConfigurationImpl.class); if (additionalConfigPaths_ != null) { for (String configPath : additionalConfigPaths_) { configuration.load(configPath); } } setUpConfiguration(configuration); super.preInit(sce); } }; ymirListener_.contextInitialized(new ServletContextEvent( servletContext_)); container_ = SingletonS2ContainerFactory.getContainer(); ymir_ = YmirContext.getYmir(); } public void setUp() { // 後方互換性のためこうしている。 } File findWebappRoot(File dir) { return new File(findProjectRoot(dir), webappRoot_); } File findProjectRoot(final File file) { File f = file; do { if (new File(f, "pom.xml").exists()) { return f; } } while ((f = f.getParentFile()) != null); return file; } /** * テストメソッドを呼び出した後の処理を行ないます。 */ protected void tearDownYmir() { ymirListener_ .contextDestroyed(new ServletContextEvent(servletContext_)); } /** * テストに使用されるS2Containerのうち、指定されたパスに対応するものを返します。 *既に構築済みのコンテナグラフのノードのうち、指定されたパスに対応するコンテナを返します。 * コンテナグラフに含まれていないコンテナを返すことはありません。 *
* * @param path パス。nullを指定してはいけません。 * @return S2Containerオブジェクト。 * @throws IllegalArgumentException S2Containerが見つからなかった場合。 */ public S2Container getContainer(String path) throws IllegalArgumentException { S2Container container = findContainer(path, getContainer()); if (container == null) { throw new IllegalArgumentException("Can't find container: " + path); } return container; } private S2Container findContainer(String path, S2Container container) { if (container.getPath().endsWith("/" + path)) { return container; } int size = container.getChildSize(); for (int i = 0; i < size; i++) { S2Container c = findContainer(path, container.getChild(i)); if (c != null) { return c; } } return null; } /** * テストに使用されるS2Containerからコンポーネントを取り出して返します。 * * @return コンポーネント。 */ public Object getComponent(Object componentKey) { return getContainer().getComponent(componentKey); } /** * テストに使用されるS2Containerからコンポーネントを取り出して返します。 * * @return コンポーネント。 */ @SuppressWarnings("unchecked") public返されるコンテナは、コンテナグラフのルートコンテナです。 *
* * @return S2Containerオブジェクト。 */ public S2Container getContainer() { return container_; } /** * テストに使用されるYmirオブジェクトを返します。 * * @return Ymirオブジェクト。 * @since 1.0.2 */ public Ymir getYmir() { return ymir_; } /** * テストに使用されるServletContextオブジェクトを返します。 * * @return ServletContextオブジェクト。 */ public ServletContext getServletContext() { return servletContext_; } /** * 現在のHttpServletRequestオブジェクトを返します。 *このメソッドはprocess
メソッドの引数に渡す
* {@link RequestInitializer}のinitialize
* メソッドの中、またはprocess
メソッドの呼び出し後に呼び出して下さい。
*
このメソッドはgetHttpSession(true)
と同じです。
*
create
がtrueの場合、セッションが作成されていなければ作成します。
*
このメソッドはprocess
メソッドの引数に渡す
* {@link RequestInitializer}のinitialize
* メソッドの中、またはprocess
メソッドの呼び出し後に呼び出して下さい。
*
このメソッドはprocess
メソッドの引数に渡す
* {@link RequestInitializer}のinitialize
* メソッドの中、またはprocess
メソッドの呼び出し後に呼び出して下さい。
*
テストケースのテストメソッドからこのメソッドを呼び出すことで、 * Ymirにリクエストを処理させることができます。 *
*通常の(文字列型の)リクエストパラメータは引数で指定できません。 * 予め{@link #acceptRequest(String, HttpMethod, RequestInitializer)} * を使ってHttpServletRequestに設定しておいて下さい。 *
*dispatcherとして{@link Dispatcher#REQUEST}以外を指定することで、 * クライアントからのリクエストだけでなくforwardやincludeといった * サーブレットコンテナの中でのディスパッチを処理させることもできます。 *
* * @param servletContext ServletContextオブジェクト。 * @param httpRequest HttpServletRequestオブジェクト。 * @param httpResponse HttpServletResponseオブジェクト。 * @param dispatcher Dispatcherオブジェクト。 * @param path コンテキスト相対のリクエストパス。リクエストパラメータを付与することはできません。 * @param method HTTPメソッド。 * @param fileParameterMap ファイル型のリクエストパラメータを持つMap。 * nullを指定することもできます。 * @param chain Ymirの処理が終わった後に処理を渡すための{@link FilterChain}オブジェクト。 * nullを指定してはいけません。 * @throws IOException I/Oエラーが発生した場合。 * @throws ServletException サーブレットエラーが発生した場合。 */ public void process(ServletContext servletContext, HttpServletRequest httpRequest, HttpServletResponse httpResponse, Dispatcher dispatcher, String path, HttpMethod method, Mapテストケースのテストメソッドからこのメソッドを呼び出すことで、 * Ymirにリクエストを処理させることができます。 *
* * @param path コンテキスト相対のリクエストパス。クエリストリングを付与することもできます。 * @param method HTTPメソッド。 * @param initializer RequestInitializerオブジェクト。 * nullを指定することもできます。 * リクエストを受け付けた直後になんらかの初期化処理をしたい場合はinitializerを指定して下さい。 * @param chain Ymirの処理が終わった後に処理を渡すための{@link FilterChain}オブジェクト。 * nullを指定してはいけません。 * @param param 1つめのリクエストパラメータの名前。 * @param params 1つめのリクエストパラメータの値、2つめのリクエストパラメータの名前、 * 2つめのリクエストパラメータの値、…の配列。 * @return Ymirの処理の後FilterChainが呼び出された場合、引数で与えたFilterChainオブジェクト。 * Ymirがレスポンスを自前で処理した場合などFilterChainが呼び出されなかった場合、null。 * @throws IOException I/Oエラーが発生した場合。 * @throws ServletException サーブレットエラーが発生した場合。 */ public FilterChain process(String path, HttpMethod method, RequestInitializer initializer, MockFilterChain chain, String param, Object... params) throws IOException, ServletException { if (chain == null) { chain = new MockFilterChainImpl(); } List