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"));

    }
}

で、

まぁせっかく作ったから使うかな。