Slim3のcontrollerでDIコンテナっぽく?テストする方法を考えていたけど必要なかった件
Slim3でDIコンテナっぽくテストする方法 - テツ日記
のひがさんのコメント読んでから
「モックとか無くても良くね?」
って感じなんだけど。
一応自分もcontrollerをテストする仕組みを作ってたので晒してみる。
ServiceLocatorでDIの代わりをさせようと思った。
※ServiceLocatorの理解が正しいかどうかは不明
で、超テキトーなの作った。
- スピンアップに影響与えないように、遅延ロード
- サービスのコンストラクタは引数無しのみ対応
- @Resourceアノテーション付きのpublicプロパティのみインジェクション対象
- インスタンス管理はすべてSingleton
public class ServiceLocator { private static final Map<Class, Object> cache = new HashMap<Class, Object>(); public static <T> T get(Class<T> clazz) { T i = (T) cache.get(clazz); if (i == null) { synchronized (ServiceLocator.class) { i = (T) cache.get(clazz); if (i == null) { try { i = clazz.newInstance(); cache.put(clazz, i); // @Resourceが付いてるpublicプロパティのみインジェクション対象 for (Field field : clazz.getFields()) { if (field.getAnnotation(Resource.class) != null) { String fieldName = field.getName(); Class fieldType = field.getType(); field.set(i, get(fieldType)); } } } catch (InstantiationException e) { throw new RuntimeException(e); } catch (IllegalAccessException e) { throw new RuntimeException(e); } } } } return i; } public static void put(Class clazz, Object o) { cache.put(clazz, o); } }
ControllerはServiceLocatorからServiceを取得する。
テストを作る時は事前にモックをServiceLocatorにputしておけばOK。
public class IndexController extends Controller { //↓コレ private TestService testService = ServiceLocator.get(TestService.class); @Override public Navigation run() throws Exception { List<Test> tests = testService.search(); requestScope("tests", tests); return forward("index.jsp"); } }
Serviceは@Resourceでインジェクションしてもらう。
まぁ普通にnewしてもいいんだけど、せっかくServiceLocatorがSingletonで持ってくれているので。
テストは普通に作れば良い。
public class TestService { private TestMeta m = TestMeta.get(); //↓コレ @Resource public Test2Service test2Service; public List<Test> search() { return Datastore.query(m).asList(); } }
Controllerのテストを作る
インターフェースとか面倒くさいから、EasyMockのClass Extensionを使う。
//↓EasyMockのClass Extension import static org.easymock.classextension.EasyMock.*; import static org.easymock.EasyMock.*; public class IndexControllerTest extends ControllerTestCase { @Test public void run() throws Exception { //モック作成 TestService mock = createMock(TestService.class); ServiceLocator.put(TestService.class, mock); expect(mock.search()).andReturn(new ArrayList()); replay(mock); //テスト実行 tester.start("/"); verify(mock); IndexController controller = tester.getController(); assertThat(controller, is(notNullValue())); assertThat(tester.isRedirect(), is(false)); assertThat(tester.requestScope("tests"), is(notNullValue())); assertThat(tester.getDestinationPath(), is("/index.jsp")); } }
で、
まぁせっかく作ったから使うかな。