/* * Copyright 2004-2015 the Seasar Foundation and the Others. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, * either express or implied. See the License for the specific language * governing permissions and limitations under the License. */ package org.seasar.framework.unit; import java.lang.reflect.Method; import java.util.Map; import org.seasar.extension.unit.S2TestCase; import org.seasar.framework.aop.Pointcut; import org.seasar.framework.aop.interceptors.MockInterceptor; import org.seasar.framework.container.AspectDef; import org.seasar.framework.container.factory.AspectDefFactory; import org.seasar.framework.env.Env; import org.seasar.framework.exception.NoSuchMethodRuntimeException; import org.seasar.framework.unit.annotation.Mock; import org.seasar.framework.unit.annotation.Mocks; import org.seasar.framework.unit.impl.OgnlExpression; import org.seasar.framework.util.ClassUtil; import org.seasar.framework.util.MethodUtil; import org.seasar.framework.util.StringUtil; import org.seasar.framework.util.tiger.CollectionsUtil; /** * テンプレートメソッドパターンに従った手続きでEasyMockを利用できるようにサポートするクラスです。 * * @author koichik */ public abstract class S2TigerTestCase extends S2TestCase { // instance fields /** EasyMockとの対話をサポートするオブジェクト */ protected EasyMockSupport easyMockSupport = new EasyMockSupport(); /** * インスタンスを構築します。 */ public S2TigerTestCase() { } /** * 名前を指定してインスタンスを構築します。 * * @param name * テストケースの名前 */ public S2TigerTestCase(final String name) { super(name); } @Override public void runBare() throws Throwable { easyMockSupport.clear(); super.runBare(); } @Override protected void setUpForEachTestMethod() throws Throwable { super.setUpForEachTestMethod(); applyMockInterceptor(getTargetMethod()); easyMockSupport.bindMockFields(this, getContainer()); } @Override protected void doRunTest() throws Throwable { final boolean recorded = doRecord(); if (recorded) { easyMockSupport.replay(); } super.doRunTest(); if (recorded) { easyMockSupport.verify(); easyMockSupport.reset(); } } /** * モックの振る舞いを記録します。 * * @return モックの振る舞いが記録するメソッドが存在する場合<code>true</code>、存在しない場合<code>false</code> * @throws Throwable * 何らかの例外またはエラーが発生した場合 */ protected boolean doRecord() { final String targetName = getTargetName(); if (!StringUtil.isEmpty(targetName)) { try { final Method method = ClassUtil.getMethod(getClass(), "record" + targetName, null); MethodUtil.invoke(method, this, null); return true; } catch (final NoSuchMethodRuntimeException ignore) { } } return false; } @Override protected void tearDownForEachTestMethod() throws Throwable { easyMockSupport.unbindMockFields(this); super.tearDownForEachTestMethod(); } /** * デフォルトのモックを作成します。 * * @param <T> * モックの型 * @param clazz * モックの対象となるクラス * @return 作成されたモック */ protected <T> T createMock(final Class<T> clazz) { return easyMockSupport.createMock(clazz); } /** * Niceモードのモックを作成します。 * * @param <T> * モックの型 * @param clazz * モックの対象となるクラス * @return 作成されたモック */ protected <T> T createNiceMock(final Class<T> clazz) { return easyMockSupport.createNiceMock(clazz); } /** * Strictモードのモックを作成します。 * * @param <T> * モックの型 * @param clazz * モックの対象となるクラス * @return 作成されたモック */ protected <T> T createStrictMock(final Class<T> clazz) { return easyMockSupport.createStrictMock(clazz); } /** * <code>method</code>に注釈された{@link Mock}に従い、コンポーネントに{@link MockInterceptor モックインターセプター}を適用します。 * * @param method * テストメソッド */ protected void applyMockInterceptor(final Method method) { final Mock mock = method.getAnnotation(Mock.class); if (mock != null) { applyMockInterceptor(mock, method); } else { final Mocks mocks = method.getAnnotation(Mocks.class); if (mocks != null) { for (final Mock each : mocks.value()) { applyMockInterceptor(each, method); } } } } /** * <code>mock</code>に従い、コンポーネントに{@link MockInterceptor モックインターセプター}を適用します。 * * @param mock * モックインターセプターの定義 * @param method * テストメソッド */ protected void applyMockInterceptor(final Mock mock, final Method method) { final MockInterceptor mi = new MockInterceptor(); if (!StringUtil.isEmpty(mock.returnValue())) { final Expression exp = createExpression(mock.returnValue(), method); mi.setReturnValue(exp.evaluate()); } if (!StringUtil.isEmpty(mock.throwable())) { final Expression exp = createExpression(mock.throwable(), method); final Object result = exp.evaluate(); mi.setThrowable(Throwable.class.cast(result)); } Pointcut pc = null; if (StringUtil.isEmpty(mock.pointcut())) { pc = AspectDefFactory.createPointcut(mock.target()); } else { pc = AspectDefFactory.createPointcut(mock.pointcut()); } final Object componentKey = StringUtil.isEmpty(mock.targetName()) ? mock .target() : mock.targetName(); final AspectDef aspectDef = AspectDefFactory.createAspectDef(mi, pc); addAspecDef(componentKey, aspectDef); } /** * 式を作成します。 * * @param source * 式の文字列表現 * @param method * テストメソッド * @return 式 */ protected Expression createExpression(final String source, final Method method) { final Map<String, Object> ctx = CollectionsUtil.newHashMap(); ctx.put("ENV", Env.getValue()); ctx.put("method", method); return new OgnlExpression(source, this, ctx); } /** * S2コンテナから<code>componentKey</code>をキーにして取得できるコンポーネント定義に<code>aspectDef</code>で表されるアスペクト定義を追加します。 * * @param componentKey * コンポーネントのキー * @param aspectDef * アスペクト定義 */ protected void addAspecDef(final Object componentKey, final AspectDef aspectDef) { getContainer().getComponentDef(componentKey).addAspectDef(0, aspectDef); } /** * EasyMockの利用に必要な一連のメソッド呼び出しを1つのテンプレートメソッドとして提供する抽象クラスです。 * * @author taedium */ protected abstract class Subsequence { /** * テストを実行します。 * <p> * テストは次の順序で行われます。 * <ul> * <li>モックの振る舞いの記録する</li> * <li>モックのモードをreplayモードに設定する</li> * <li>モックとのインタラクションを再現する</li> * <li>モックとのインタラクションを検証する</li> * <li>モックをリセットする</li> * </ul> * </p> * * @throws Exception * 何らかの例外が発生した場合 */ public void doTest() throws Exception { record(); easyMockSupport.replay(); replay(); easyMockSupport.verify(); easyMockSupport.reset(); } /** * モックとのインタラクションを再現します。 * * @throws Exception * 何らかの例外が発生した場合 */ protected abstract void replay() throws Exception; /** * モックの振る舞いを記録します。 * * @throws Exception * 何らかの例外が発生した場合 */ protected void record() throws Exception { } } }