CDI を使っているクラスを Unit Test をするために、Wedl SE を使ってみました。
Weld: Download
Weld SE では、RequestScoped や SessionScoped のスコープが使えません。
Chapter 18. Application servers and environments supported by Weld
今回テスト対象のクラスは RequestScoped を使っていたので、このままでは動きません。
オブジェクトの生存期間は考慮せず、とりあえず Injection したいだけだったので下記サイトを参考に適当に実装してみました。
Simulating CDI's Session and Request Scope in a J2SE app | Dan Haywood
Weld-SE のバージョンは 2.1.2 Final を使ってます。
The Central Repository Search Engine
サンプルコードは相変わらず Xtend です。
Junit の Rule を使って Weld の生成/破棄を管理しています。
package sample.junit import javax.enterprise.inject.spi.Extension import org.jboss.weld.environment.se.Weld import org.jboss.weld.environment.se.WeldContainer import org.junit.rules.ExternalResource import javax.enterprise.event.Observes import javax.enterprise.inject.spi.AfterDeploymentValidation import javax.enterprise.inject.spi.BeanManager import javax.enterprise.context.SessionScoped import javax.enterprise.context.RequestScoped import java.lang.annotation.Annotation import org.jboss.weld.manager.BeanManagerHelper import org.jboss.weld.context.AbstractBoundContextHelper class CDIInjector extends ExternalResource { var Weld weld var WeldContainer container override protected before() throws Throwable { weld = new Weld weld.addExtension(new WeldServletScopesSupportExtension) container = weld.initialize } override protected after() { weld.shutdown } def <T> getInstance(Class<T> clazz) { container.instance.select(clazz).get } } package class WeldServletScopesSupportExtension implements Extension { def afterDeployment(@Observes AfterDeploymentValidation event, BeanManager beanManager) { setContextActive(beanManager, SessionScoped); setContextActive(beanManager, RequestScoped); } private def setContextActive(BeanManager beanManager, Class<? extends Annotation> cls) { val context = BeanManagerHelper.getContext(beanManager, cls) AbstractBoundContextHelper.activate(context) } }
Weld に addExtension でカスタム拡張を追加しています。
カスタム拡張が何をしているかというと、SessionScoped と RequestScoped を扱う Context を Active(デフォルトでは非Activeのため動かない)にしています。
public ではない API を使ってるので、Context を引っ張ってくるクラス/Active 化するクラスは パッケージを合わせています。
Context を取ってくるところはこちら。
package org.jboss.weld.manager import javax.enterprise.inject.spi.BeanManager import org.jboss.weld.bean.builtin.BeanManagerProxy import java.lang.annotation.Annotation import org.jboss.weld.context.AbstractBoundContext import javax.enterprise.context.spi.Context import org.jboss.weld.util.ForwardingContext class BeanManagerHelper { def static getContext(BeanManager beanManager, Class<? extends Annotation> cls) { val proxy = beanManager as BeanManagerProxy val impl = proxy.delegate unwrapContext(impl.getContexts().get(cls).get(0)) } private static def AbstractBoundContext<?> unwrapContext(Context context) { switch (context) { AbstractBoundContext<?> case true: return context ForwardingContext case true: unwrapContext(ForwardingContext.unwrap(context)) default: throw new Exception('''«context.class»''') } } }
BeanManagerProxy は決め打ち。
BeanManagerImple.getContexts が protected でした。
Context を Active 化するコードはこちら。
package org.jboss.weld.context import java.util.HashMap import org.jboss.weld.context.beanstore.MapBeanStore import org.jboss.weld.context.beanstore.SimpleNamingScheme import org.jboss.weld.context.bound.BoundRequestContext import org.jboss.weld.context.bound.BoundSessionContext class AbstractBoundContextHelper { def static void activate(AbstractBoundContext<?> context) { context.beanStore = new MapBeanStore(getNamingScheme(context), new HashMap) context.activate } def static getNamingScheme(AbstractBoundContext<?> context) { new SimpleNamingScheme(switch (context) { BoundRequestContext case true: BoundRequestContext.name BoundSessionContext case true: BoundSessionContext.name }) } }
生存期間を管理しないので、Store には HashMap を使ってます。
こちらは AbstractBoundContext.setBeanStore が protected です。
テストするクラスはこちら。JAX-RS 使ってますがどうでもいいです。
package sample.weldsetest import javax.enterprise.context.RequestScoped import javax.inject.Inject import javax.ws.rs.GET import javax.ws.rs.Path import javax.ws.rs.QueryParam import javax.enterprise.context.ApplicationScoped @RequestScoped @Path("hello") class Hello { @Inject Dao dao @GET def String sayHello(@QueryParam("id") String id) '''Hello, «dao.getName(id)»''' } interface Dao { def String getName(String id) } @ApplicationScoped class SimpleDaoImpl implements Dao { override getName(String id) '''Name:«id»''' }
jUnit のテストがこちら
package sample.weldsetest import org.junit.Test import sample.junit.CDIInjector import static extension xtendjunit.AssertExtensions.* import org.junit.Rule class HelloTest { @Rule public extension CDIInjector injector = new CDIInjector @Test def void sayHello() { val target = getInstance(Hello) target.sayHello("aa").is("Hello, Name:aa") } }
ビルドは gradle 使ってます。
apply plugin: 'java' apply plugin: 'war' repositories { mavenCentral() } dependencies { compile ('org.eclipse.xtend:org.eclipse.xtend.lib:2.5.3') { exclude module: 'guava' } compile 'com.google.guava:guava:16.0.1' testCompile 'org.jboss.weld.se:weld-se:2.1.2.Final' testCompile 'junit:junit:4.11' testCompile files('c:/work/github/xtendjunit/build/libs/xtendjunit-0.0.1.jar') providedCompile 'javax:javaee-web-api:7.0' } sourceSets { main { java { srcDir 'src/main/xtend-gen' } } test { java { srcDir 'src/test/xtend-gen' } } } sourceCompatibility = targetCompatibility = '1.7' def defaultEncoding = 'UTF-8' tasks.withType(AbstractCompile) each { it.options.encoding = defaultEncoding } webAppDirName = "WebContent"
Eclipse で jUnit を動かす時は、クラスパスの設定に注意が必要でした。
Gradle の依存関係を GlassFish より上にしておかないと、weld のバージョンが 2.0.0 になり動かなくなりました。