/*
* 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 {
}
}
}