kuniku’s diary

はてなダイアリーから移行(旧 d.hatena.ne.jp/kuniku/)、表示がおかしな箇所はコメントをお願いします。記載されている内容は日付およびバージョンに注意してください。直近1年以上前は古い情報の可能性が高くなります。

Spring + struts の単体テスト つづき(1)

昨日のつづきで、springとstrutsを連携させたテストがなんとかできそう。

web.xmlの記述の順番が悪いとか、いろいろあるのだけど
昨日ので とりあえず web.xmlのパースには問題ないけれども

springのコンテナをどうやって初期化(コンテナの生成?)すればよいのやら・・・・と

googleさんの検索にまかせて調べていると
http://raibledesigns.com/downloads/appfuse/api/org/appfuse/webapp/action/BaseStrutsTestCase.java.html
がひっかかりました。
おっ、これは やりたいことに近いかも。

こっちの
http://homepage2.nifty.com/~inaba/Spring_StrutsTestCase.html
では、springのバージョンは1.2系だし、strutsTestCaseもほぼ同じバージョンだし、
springとstrutsの連携に、SpringのActionSupportから派生させていること とあるし、MockStrutsTestCaseを継承しているから
これを参考にすれば とやってみたけれどコンテナが生成できなくて無理だった。

なんとかしてコンテナを作成しなきゃならんってことで、探してみつけたのが先述のBaseStrutsTestCase.javaのサンプル。

これを参考にいろいろ便利にできないかなとsupport的クラスをつくってやることにした。

クラス図

以下、実装したクラス 必要なもののみ抜粋のため、記載されていないインターフェイスやクラスがある。
ActionTestUtls.java、AdminTestConstant.java、SessionBean.java、SessionBeanManager.java、ActionTestDto.java、ContextFileListUtils.java、ApplicationContextFactory.java などは割愛

テストクラスの基底となるAbstractActionTestSupport.java

import org.springframework.mock.web.MockServletContext;
import org.springframework.web.context.ContextLoader;
import org.springframework.web.context.ContextLoaderListener;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.support.WebApplicationContextUtils;

import servletunit.struts.MockStrutsTestCase;

/**
 * Spring+strutsテスト用のサポートクラス<br>
 * このクラスを継承してActionのテストクラスを作成.
 * 
 * @since   2008/04/23
 * @version 1.0 2008/04/23 14:05:53
 */
public abstract class AbstractActionTestSupport extends MockStrutsTestCase{
  
  /** <code>webXmlPath_</code> web.xmlファイルのパス */
  protected String webXmlPath_ ;
  
  /** <code>strutsConfigPath_</code> struts-configファイルのパス */
  protected String strutsConfigPath_;
  
  /** <code>applicationContextFiles_</code> springのApplicationContextのパス */
  protected String[] applicationContextFiles_;
  
  /** <code>webAppContext_</code> springのweb用コンテキスト */
  protected WebApplicationContext webAppContext_ = null;
  
  protected boolean commonContextRead_ = true;
  
  //protected static ApplicationContext ctx = new FileSystemXmlApplicationContext(new String[] { "WEB-INF/springContext.xml " }); 
  public AbstractActionTestSupport(){
    super();
  }
  
  /**
   * テストの名称を指定するStringを引数にもつコンストラクタ
   * @param testName セットするテストの名称
   */
  public AbstractActionTestSupport(String testName){
    super(testName);
  }
  
  /**
   * クラスのインスタンス生成に必要なdtoを指定し、インスタンスの生成を行う.
   * @param entity   
   */
  public AbstractActionTestSupport(ActionEntity entity){
    
    this.webXmlPath_ = entity.getWebXmlPath();
    this.strutsConfigPath_ = entity.getStrutsConfigPath();
    this.applicationContextFiles_ = entity.getApplicationContextFiles();
    this.commonContextRead_ = entity.isCommonFileRead();
    
  }
  
  @Override //個別に実装してもかまわない.
  protected void setUp() throws Exception{
    
    super.setUp();
    this.setUpConfig();
  }
  
  
  /**
   * このメソッドは、Overrideしてもかまわない.
   * 
   * Overrideしてもよいが、このクラス内のsetUp()にてこの呼び出しを行うため<br>
   * setUp()メソッドはOverrideしない または、setUp()メソッドをOverrideし、<br>
   * その中で、このメソッドを呼び出す必要があります.
   **/
  public void setUpConfig(){
    
    // コンテキストパスの指定
    setContextDirectory(new java.io.File("."));
    
    if (webXmlPath_ == null || "".equals(webXmlPath_) ){
      this.webXmlPath_ = AdminTestConstant.DEFAULT_WEB_XML_PATH;
    }
    
    // Servletコンフィグファイル(web.xml)の指定
    setServletConfigFile(webXmlPath_);
    
    if (strutsConfigPath_ == null || "".equals(strutsConfigPath_)){
      this.strutsConfigPath_ = AdminTestConstant.DEFAULT_STRUTS_CONFIG_PATH;
    }
    // Strutsコンフィグファイルの指定
    setConfigFile(this.strutsConfigPath_);
    
    MockServletContext sc = new MockServletContext();
    
    // initialize Spring
    //springのコンテキストを生成させる?
    applicationContextFiles_= ContextFileListUtils.changeContextFile4CommonFile(commonContextRead_,applicationContextFiles_);
    
    String myFile = ContextFileListUtils.array2String(applicationContextFiles_);
    String files = myFile;//ActionTestUtls.getMyFileAppendCommonFile(myFile);
    sc.addInitParameter(ContextLoader.CONFIG_LOCATION_PARAM,files);
    
    ServletContextListener contextListener = new ContextLoaderListener();
    ServletContextEvent event = new ServletContextEvent(sc);
    contextListener.contextInitialized(event);
    
    // magic bridge to make StrutsTestCase aware of Spring's Context
    getSession().getServletContext().setAttribute(
            WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, 
            sc.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE));
    
    webAppContext_ = WebApplicationContextUtils.getRequiredWebApplicationContext(
                getSession().getServletContext());
        
  }
  

}


springのコンテナを生成するインターフェイスApplicationContextFactoryの実装クラス

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

/**
 * SpringのApplicationContextを生成するファクトリーの実装クラス
 * 
 * @since   2008/04/22
 * @version 1.0 2008/04/22 14:45:45
 */
public class DefaultApplicationContextFactoryImpl implements ApplicationContextFactory {

  /** <code>testContextFile</code> 読み込むテスト対象のApplicationContextファイル */
  protected String testContextFile;

  /** <code>commonContextFile</code> 読み込む共通のApplicationContextファイル */
  protected String commonContextFile;

  /** <code>testContextFiles</code> 読み込むテスト対象のApplicationContextファイルのファイルの配列 */
  protected String[] testContextFiles;

  private static final ApplicationContextFactory instance = new DefaultApplicationContextFactoryImpl();

  protected DefaultApplicationContextFactoryImpl() {
    //不可視とはせず、継承したクラスでコンストラクタを使用できるようにした。
    //なので、インスタンスを管理する場合は注意して作成する。
    //mapなどでこのクラスのインスタンスを管理させるなど必要となる.
  }

  /**
   * インスタンスの取得
   * @return このクラスのsingletonのインスタンス
   **/
  public static ApplicationContextFactory getInstance() {
    return instance;
  }

  /* (非 Javadoc)
   */
  public ApplicationContext createAllContext() {

    ApplicationContext ctx = new ClassPathXmlApplicationContext(ApplicationContextFactory.ALL_CONTEXT_PATH);
    return ctx;

  }

  /* (非 Javadoc)
   */
  public ApplicationContext createCommonContext() {

    ApplicationContext ctx = new ClassPathXmlApplicationContext(ApplicationContextFactory.CONTEXT_COMMONFILE);
    return ctx;

  }

  /* (非 Javadoc)
   */
  public ApplicationContext create(String testContextFile) {

    this.testContextFile = testContextFile;
    ApplicationContext ctx = new ClassPathXmlApplicationContext(testContextFile);
    return ctx;

  }

  /* (非 Javadoc)
   */
  public ApplicationContext create(String commonContextFile, String testContextFile) {

    //String files[] = {new String(commonContextFile),new String(testContextFile)};
    String files[] = { testContextFile };
    testContextFiles = files;
    return create(commonContextFile, testContextFiles);

  }

  /* (非 Javadoc)
   */
  public ApplicationContext create(String commonContextFile, String[] testContextFiles) {

    //指定なしの場合はデフォルトを使用する.
    if (commonContextFile == null || "".equals(commonContextFile)) {
      commonContextFile = CONTEXT_COMMONFILE;
    }

    if (testContextFiles == null || testContextFiles.length == 0) {
      System.err.println("testContextFilesの指定がありません。testContextFile=" + testContextFile);
      System.err.println("testContextFilesの指定がありません。testContextFiles.length=" + testContextFiles.length);
      throw new RuntimeException();
    }

    String files[] = addStringArray(testContextFiles, commonContextFile);
    return create(files);

  }

  /* (非 Javadoc)
   */
  public ApplicationContext create(String[] contextFiles) {

    if (contextFiles == null || contextFiles.length == 0) {
      System.err.println("testContextFilesの指定がありません。testContextFile=" + testContextFile);
      System.err.println("testContextFilesの指定がありません。testContextFiles.length=" + testContextFiles.length);
      throw new RuntimeException();
    }

    ApplicationContext ctx = new ClassPathXmlApplicationContext(contextFiles);
    return ctx;

  }

  /**
   * String配列に、1つのStringを末尾に追加する
   * @param files 
   * @param addString 
   * @return
   **/
  private String[] addStringArray(String[] files, String addString) {

    String newFiles[] = new String[(files.length + 1)];
    /*
    int i = 0;
    for(; i < files.length; i++){ //ArrayListに入っている要素数だけ繰り返す
      newFiles[i] = files[i];
    }
    newFiles[i] = addString;
     */
    System.arraycopy(files, 0, newFiles, 0, files.length); //(3)

    newFiles[files.length] = addString;

    // 結果を確認する
    for (int i = 0; i < newFiles.length; i++) {
      System.out.println(newFiles[i]);
    }
    return newFiles;
  }

}


コンテナ生成に必要なファイルやweb.xmlstrutsの設定ファイルのパスを保持するvalueオブジェクトを規程したインターフェイス

/**
 * テストクラスのインスタンス生成時に渡す設定ファイルのパス情報を格納するvalueオブジェクト
 * 
 * @since   2008/04/23
 * @version 1.0 2008/04/23 14:06:02
 */
public interface ActionEntity {
  
  /**
   * web.xmlのパスを戻します。
   * @return
   **/
  public String getWebXmlPath();
  
  
  /**
   * web.xmlのパスを設定します
   * @param path 
   **/
  public void setWebXmlPath(String path);
  
  
  /**
   * applicationContextのxmlファイルの配列を戻します。
   * @return
   **/
  public String[] getApplicationContextFiles();
  
  
  /**
   * ApplicationContextFilesを設定します
   * @param files 
   **/
  public void setApplicationContextFiles(String[] files);
  
  
  /**
   * struts-configのパスを戻します。
   * @return
   **/
  public String getStrutsConfigPath() ;

  
  /**
   * struts-configのパスを設定します
   * @param path 
   **/
  public void setStrutsConfigPath(String path);

  /**
   * commonFileReadを取得します。
   * @return commonFileRead
   */
  public boolean isCommonFileRead() ;

  /**
   * commonFileReadを設定します。
   * @param commonFileRead commonFileRead
   */
  public void setCommonFileRead(boolean commonFileRead);
}


テストクラス(テスト対象のActionのテスト)

public class DecActionTest extends AbstractActionTestSupport{
  
  /* (非 Javadoc)
   */
  @Override
  protected void setUp() throws Exception {
    
    super.setUp();
    setInitParameter("validating","false");
  }
  
  
  public DecActionTest2(){
    super(createActionEntity());
  }
  
  public void testSuccessfulLogin() {

    addRequestParameter("userId","abcd");
    addRequestParameter("password","aiueo");

    getSession().setAttribute("SessionBean", createSessionBean());
    
    setRequestPathInfo("/Transfer");
    actionPerform();
    verifyForward("success");
    verifyForwardPath("/WEB-INF/jsp/test/test1.jsp");
    //assertEquals("deryl",getSession().getAttribute("authentication"));
    verifyNoActionErrors();
  }
  
  private static ActionEntity createActionEntity(){
    
    ActionTestDto entity = new ActionTestDto();
    String files[] = {
        "/WEB-INF/conf/spring/applicationContext-dev.xml",
        "/WEB-INF/conf/spring/applicationContext-common.xml"};
    
    entity.setApplicationContextFiles(files);
    entity.setStrutsConfigPath("/WEB-INF/conf/struts/struts-config-test.xml");
    entity.setWebXmlPath(AdminTestConstant.DEFAULT_WEB_XML_PATH);
    entity.setCommonFileRead(true);//commonを読み込ませない場合はfalseにするが通常はセットする必要なし
    return (ActionEntity)entity;
    
  }
  
  /**
   * httpにおけるユーザ情報を格納したsessionを生成します.
   * 
   * @return
   **/
  private SessionBean createSessionBean(){
  
    //sessionに格納するbeanを生成するクラスから生成する
    SessionBean bean = SessionBeanManager.createSessionBean();
    bean.setUserId("ID0001");//ユーザidをセットします.
    
    return bean;
  }
  
}