#freeze
[[FrontPage]]

#contents

2008/03/13からのアクセス回数 &counter;

* Spring-MVCでspringのsession scopeオブジェクトをテストしたい [#caea628a]
Spring 2.0から導入されたsession scopeオブジェクトは、DispatcherServletで処理されるため
MockHttpServletRequestを使った単体テストでは、bean定義ファイルでscope="session"で
定義されたsession scopeオブジェクトのsession単位での持ち回りのテストができません。

** session scopeオブジェクトに必要なもの [#z1d0df69]
session scopeオブジェクトのテストには、

-- GenericWebApplicationContextをcreateApplicationContextメソッドで返す
-- WebApplicationContextのbeanFactoryにSCOPE_REQUEST, SCOPE_SESSION, SCOPE_GBLOBAL_SESSIONスコープを登録する
-- HTTPリクエストを処理する前に、WebApplicationContextにサーブレットコンテキストをセットし、サーブレットのROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTEにWebApplicationContextをセットする
-- HTTPリクエストの処理の後に、RequestContextFilterと同じ処理を追加する

*** AbstractTransactionalSpringWebContextTestsを追加 [#h0cadda5]
GenericWebApplicationContextをcreateApplicationContextメソッドで返すTestCaseとして
AbstractTransactionalSpringWebContextTestsを追加しました。

AbstractTransactionalSpringWebContextTestsのcreateApplicationContextは以下の通りです。
#pre{{
	protected ConfigurableApplicationContext createApplicationContext(final String[] locations) {
		GenericWebApplicationContext context = 
				new GenericWebApplicationContext(
						new GenericApplicationContext().getDefaultListableBeanFactory());
		context.setServletContext(new MockServletContext());
		customizeBeanFactory(context.getDefaultListableBeanFactory());
		createBeanDefinitionReader(context).loadBeanDefinitions(locations);
		context.refresh();
		// StaticWebApplicationContext のpostProcessBeanFactoryと同じ処理を追加
		ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
		beanFactory.registerScope(WebApplicationContext.SCOPE_REQUEST, new RequestScope());
		beanFactory.registerScope(WebApplicationContext.SCOPE_SESSION, new SessionScope(false));
		beanFactory.registerScope(WebApplicationContext.SCOPE_GLOBAL_SESSION, 
				new SessionScope(true));

		return context;
	}
}}

*** beginRequest, endRequestメソッドの追加 [#l7d22f78]
HTTPリクエストの前後にRequestContextFilterと同様の処理行うメソッド、beginRequest, endRequestメソッドをAbstractTransactionalSpringWebContextTestsに定義しました。

beginRequestメソッドは、
#pre{{
	public void beginRequest(MockHttpServletRequest req) {		
		ServletRequestAttributes attributes = new ServletRequestAttributes(req);
		req.setAttribute(REQUEST_ATTRIBUTES_ATTRIBUTE, attributes);
		RequestContextHolder.setRequestAttributes(attributes);
		
		ServletContext sc = req.getSession().getServletContext();
		GenericWebApplicationContext wac = (GenericWebApplicationContext)getApplicationContext();
		wac.setServletContext(sc);
		sc.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, (GenericWebApplicationContext)getApplicationContext());
		
	}
}}

endRequestメソッドは、
#pre{{
	public void endRequest(MockHttpServletRequest req) {
		ServletRequestAttributes attributes = (ServletRequestAttributes) req
				.getAttribute(REQUEST_ATTRIBUTES_ATTRIBUTE);
		ServletRequestAttributes threadAttributes = (ServletRequestAttributes) RequestContextHolder
				.getRequestAttributes();
		if (threadAttributes != null) {
			// We're assumably within the original request thread...
			if (attributes == null) {
				attributes = threadAttributes;
			}
			RequestContextHolder.setRequestAttributes(null);
			LocaleContextHolder.setLocale(null);
		}
		if (attributes != null) {
			attributes.requestCompleted();
			if (logger.isDebugEnabled()) {
				logger.debug("Cleared thread-bound request context: " + req);
			}
		}
	}
}}

と定義しました。

** テストメソッドの記述方法 [#r94c1f00]

session scopeオブジェクトのテストメソッドを定義するには、
-- AbstractTransactionalSpringWebContextTestsのサブクラスとしてTestCaseを定義する
-- HTTP要求を処理するhadleRequestの前後をbeginRequest, endRequestで挟む
-- 同一セッション内の連続する処理をテストする場合には、直前のsessionをMockHttpServletRequestにセットする

*** HTTP要求の前後処理例 [#a392de6c]
以下にMemberOpsControllerを例にHTTP要求の前後処理の仕方を示します。
#pre{{
		MemberOpsController	memberOpsController = 
				(MemberOpsController)getApplicationContext().getBean("memberOpsController");
		ModelAndView mv = null;
		beginRequest(req);
		mv = memberOpsController.handleRequest(req,new MockHttpServletResponse());
		endRequest(req);
}}


*** セッション情報の持ち回り [#h311f391]

同一セッションは、つぎのようにして持ち回ることができます。
#pre{{
		HttpSession session =  req.getSession();
		req = new MockHttpServletRequest("POST","memberops/list.htm");
		req.setSession(session);
}}

これで、session scopeオブジェクトの単体テストができるようになります。

** サンプルの紹介 [#d85f1193]
このページが分かりづらいとのご指摘が多いので、TestCase例で説明します。

- maven-GenMVC-pluginに入っている&ref(AbstractTransactionalSpringWebContextTests.java);
- maven-GenMVC-pluginで生成されるテストケースひな形&ref(TestCase.java);

を参考にしながら、以下の説明を見てください。

*** テストケースの作成 [#bcc68de6]
AbstractTransactionalSpringWebContextTestsのサブクラスとして定義します。

#pre{{
public class TestCase
	extends AbstractTransactionalSpringWebContextTests {	
}}

*** Sessionを後のリクエストに継承する例 [#m84614cd]
次にSessionを後のリクエストに継承するメソッドを示します。

- HTTP要求の前後にbeginRequest, endRequestで囲む

のは、SpringではフィルタRequestContextFilterの処理と同じこと処理を実現するためです。
例では、handleRequestの前後に挿入してあります。

#pre{{
		try {
			beginRequest(req);
			mv = memberOpsController.handleRequest(req,new MockHttpServletResponse());
			endRequest(req);
		} catch (Exception err) {
			err.printStackTrace();
		}
}}

- セッションの引き継ぎ

は、新しく生成したMockHttpServletRequestに前回の要求のsessionをセットすることで再現します。

#pre{{
		HttpSession session =  req.getSession();
		req = new MockHttpServletRequest("POST","memberops/list.htm");
		// sessionを次の要求に引き継ぐ
		req.setSession(session);
}}


念のために、testmemberOpsController_Listのメソッドを示します。
#pre{{
	//	memberOpsController list method test
	public void testmemberOpsController_List() {
		dbUnitHelperDao.restore("src/test/resources/dump.xml");

		MockHttpServletRequest req = new MockHttpServletRequest("POST","memberops/list.htm");

		MemberOpsController	memberOpsController = (MemberOpsController)getApplicationContext().getBean("memberOpsController");
		ModelAndView mv = null;
		try {
			beginRequest(req);
			mv = memberOpsController.handleRequest(req,new MockHttpServletResponse());
			endRequest(req);
		} catch (Exception err) {
			err.printStackTrace();
		}
		Member expectedMember = (Member)getApplicationContext().getBean("expectedMember");

		assertNull(mv.getViewName());
		Member[] members = (Member[])mv.getModel().get("memberList");
		assertEquals(memberManager.print(expectedMember), memberManager.print(members[0]));		

		HttpSession session =  req.getSession();
		req = new MockHttpServletRequest("POST","memberops/list.htm");
		// sessionを次の要求に引き継ぐ
		req.setSession(session);
		memberOpsController = (MemberOpsController)getApplicationContext().getBean("memberOpsController");
		mv = null;
		try {
			beginRequest(req);
			mv = memberOpsController.handleRequest(req,new MockHttpServletResponse());
			endRequest(req);
		} catch (Exception err) {
			err.printStackTrace();
		}
		expectedMember = (Member)getApplicationContext().getBean("expectedMember");

		assertNull(mv.getViewName());
		members = (Member[])mv.getModel().get("memberList");
		assertEquals(memberManager.print(expectedMember), memberManager.print(members[0]));		
	}

}}

** コメント [#y379a8a1]
この記事は、

#vote(おもしろかった[3],そうでもない[1],わかりずらい[17])
#vote(おもしろかった[3],そうでもない[1],わかりずらい[18])

皆様のご意見、ご希望をお待ちしております。
- 分かりづらいとのコメントに対し、「サンプルの紹介」とサンプルソースを追加しました。 -- [[竹本 浩]] &new{2009-05-20 (水) 14:56:26};

#comment_kcaptcha

トップ   編集 差分 バックアップ 添付 複製 名前変更 リロード   新規 一覧 単語検索 最終更新   ヘルプ   最終更新のRSS
SmartDoc