CLOVER🍀

That was when it all began.

wildfly-arquillian-container-embeddedってwildfly-arquillian-container-managedと何か違うんですか?

最近、組み込みTomcatJAX-RSCDIを載せて動かしていることが多いのですが、ちょっとしたテストの時に便利だなぁという感じで使っています。ただ、Java EE系のテストといえばArquillianだということで、ふと見た時に気になったのがこの人。

WildFly 8.1.0 - Embedded
https://docs.jboss.org/author/display/ARQ/WildFly+8.1.0+-+Embedded

別に、組み込みWildfFlyというわけではありません。

このページに書かれているMavenの設定の雰囲気を見ていると、WildFlyをダウンロードしてきてそのまま実行しよう!みたいなものみたいです。

ちょっと気になったので、試してみました。

が、いつも通りここはScalaでやります。
使用しているWildFlyは、8.2.0.Finalです。sbtでやっているので、WildFlyは自分でダウンロードしたものを使いました。

ビルド設定

まずは、sbtの設定。
build.sbt

name := "arquillian-wildfly-container-embedded"

version := "0.0.1-SNAPSHOT"

scalaVersion := "2.11.6"

scalacOptions ++= Seq("-Xlint", "-unchecked", "-deprecation", "-feature")

updateOptions := updateOptions.value.withCachedResolution(true)

fork in Test := true

javaOptions ++= Seq("-Djava.util.logging.manager=org.jboss.logmanager.LogManager")  // embedded

resolvers += "JBoss Public Maven Repository Group" at "https://repository.jboss.org/nexus/content/groups/public/"

libraryDependencies ++= Seq(
  "javax" % "javaee-web-api" % "7.0" % "provided",
  "org.jboss.arquillian.junit" % "arquillian-junit-container" % "1.1.8.Final" % "test",
  "org.jboss.shrinkwrap.resolver" % "shrinkwrap-resolver-depchain" % "2.2.0-beta-2" % "test",
  "org.wildfly" % "wildfly-arquillian-container-embedded" % "8.2.0.Final" % "test",  // embedded
  // "org.wildfly" % "wildfly-arquillian-container-managed" % "8.2.0.Final" % "test",  // managed
  "org.scalatest" %% "scalatest" % "2.2.4" % "test",
  "junit" % "junit" % "4.12" % "test"
)

コメントに「// embedded」と「// managed」とありますが、Managedに切り替える時は「// embedded」と書いている行をコメントアウトすると切り替わります。

テスト対象

テスト対象のコードとして、CDI管理Bean、JAX-RS関連のクラスを作成。


CDI管理Bean。
src/main/scala/org/littlewings/javaee7/service/CalcService.scala

package org.littlewings.javaee7.service

import javax.enterprise.context.RequestScoped

@RequestScoped
class CalcService {
  def add(a: Int, b: Int): Int = a + b

  def multiply(a: Int, b: Int): Int = a * b
}

JAX-RS関連。
src/main/scala/org/littlewings/javaee7/rest/JaxrsApplication.scala

package org.littlewings.javaee7.rest

import javax.ws.rs.ApplicationPath
import javax.ws.rs.core.Application

@ApplicationPath("rest")
class JaxrsApplication extends Application

リソースクラス。
src/main/scala/org/littlewings/javaee7/rest/CalcResource.scala

package org.littlewings.javaee7.rest

import javax.enterprise.context.RequestScoped
import javax.inject.Inject
import javax.ws.rs.{ DefaultValue, GET, Path, QueryParam, Produces }
import javax.ws.rs.core.MediaType

import org.littlewings.javaee7.service.CalcService

@Path("calc")
@RequestScoped
class CalcResource {
  @Inject
  private var calcService: CalcService = _

  @GET
  @Path("add")
  @Produces(Array(MediaType.TEXT_PLAIN))
  def add(@QueryParam("a") @DefaultValue("0") a: Int,
    @QueryParam("b") @DefaultValue("0") b: Int): Int =
    calcService.add(a, b)

  @GET
  @Path("multiply")
  @Produces(Array(MediaType.TEXT_PLAIN))
  def multiply(@QueryParam("a") @DefaultValue("0") a: Int,
    @QueryParam("b") @DefaultValue("0") b: Int): Int =
    calcService.multiply(a, b)
}

まあ、リソースクラスもCDI管理対象ですが…。

これらを対象に、テストコードを書きます。

テストコード

sbtでやっているのですが、今回は依存関係も少ないのでpomは作りません。手動でいってしまいましょう。

デプロイする対象も少ないので、Deploymentを作成するトレイトを作りました。
src/test/scala/org/littlewings/javaee7/Deployer.scala

package org.littlewings.javaee7

import org.jboss.arquillian.container.test.api.Deployment
import org.jboss.shrinkwrap.api.ShrinkWrap
import org.jboss.shrinkwrap.api.asset.EmptyAsset
import org.jboss.shrinkwrap.api.spec.WebArchive
import org.jboss.shrinkwrap.resolver.api.maven.Maven

trait Deployer {
  @Deployment
  def createDeployment: WebArchive =
   ShrinkWrap
     .create(classOf[WebArchive])
     .addPackages(true, "org.littlewings.javaee7")
     .addAsLibraries {
       Maven
         .resolver
         .resolve("org.scala-lang:scala-library:2.11.6")
         .withTransitivity
         .asFile: _*
     }
     .addAsLibraries {
       Maven
         .resolver
         .resolve("org.scalatest:scalatest_2.11:2.2.4")
         .withTransitivity
         .asFile: _*
     }
}

CDI管理Beanをテストするクラス。

src/test/scala/org/littlewings/javaee7/ServiceTest.scala 
package org.littlewings.javaee7

import javax.inject.Inject

import org.jboss.arquillian.junit.Arquillian

import org.junit.runner.RunWith
import org.junit.Test
import org.scalatest.Matchers._
import org.scalatest.junit.JUnitSuite

import org.littlewings.javaee7.service._

object ServiceTest extends Deployer

@RunWith(classOf[Arquillian])
class ServiceTest extends JUnitSuite {
  @Inject
  private var calcService: CalcService = _

  @Test
  def testServiceAdd(): Unit =
    calcService.add(1, 2) should be (3)

  @Test
  def testServiceMultiply(): Unit =
    calcService.multiply(3, 4) should be (12)
}

JAX-RS側のテストをするクラス。
src/test/scala/org/littlewings/javaee7/RestTest.scala

package org.littlewings.javaee7

import scala.io.Source

import java.net.URL

import javax.ws.rs.ApplicationPath

import org.jboss.arquillian.container.test.api.RunAsClient
import org.jboss.arquillian.junit.Arquillian
import org.jboss.arquillian.test.api.ArquillianResource

import org.junit.runner.RunWith
import org.junit.Test
import org.scalatest.Matchers._
import org.scalatest.junit.JUnitSuite

import org.littlewings.javaee7.rest._

object JaxrsTest extends Deployer

@RunWith(classOf[Arquillian])
@RunAsClient
class JaxrsTest extends JUnitSuite {
  private val resourcePrefix: String =
    classOf[JaxrsApplication]
      .getAnnotation(classOf[ApplicationPath])
      .value

  @ArquillianResource
  private var deploymentUrl: URL = _

  @Test
  def testResourceAdd(): Unit =
    Source
      .fromURL(s"${deploymentUrl}${resourcePrefix}/calc/add?a=1&b=2")
      .mkString should be ("3")

  @Test
  def testResourceMultiply(): Unit =
    Source
      .fromURL(s"${deploymentUrl}${resourcePrefix}/calc/multiply?a=3&b=4")
      .mkString should be ("12")
}

このあたりは、いたって普通のArquillianを使ったテストコードです。

arquillian.xml

最後に、Arquillianの設定ファイルを用意します。
src/test/resources/arquillian.xml

<?xml version="1.0" encoding="UTF-8"?>
<arquillian xmlns="http://jboss.org/schema/arquillian"
            xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
            xsi:schemaLocation="http://jboss.org/schema/arquillian
                                http://jboss.org/schema/arquillian/arquillian_1_0.xsd">
  <container qualifier="jboss" default="true">
    <configuration>
      <property name="jbossHome">../wildfly-8.2.0.Final</property>
      <property name="modulePath">../wildfly-8.2.0.Final/modules</property> <!-- embedded -->
    </configuration>
  </container>
</arquillian>

プロパティmodulePathは、Embeddedでは必要ですが、Managedでは不要です。

WildFly自体は、今回作成したプロジェクトの隣に並べておきました。

テスト

あとは、テストを実行すればjbossHomeで指定したパスにあるWildFlyを起動してテストしてくれます。

> test

これでお終い。

ハマったこと

いや、ハマりましたよ、けっこう。

まず、arquillian.xmlに書かれたmodulePathがないと、WildFlyが起動してくれません。

      <property name="modulePath">../wildfly-8.2.0.Final/modules</property> <!-- embedded -->

Managedでは要らないのですけど…。

次に、modulePathを設定したら今度はJBoss Log Managerあたりでコケてくれます。

Cannot not load JBoss LogManager. The LogManager has likely been accessed prior to this initialization.

スタックトレース

ERROR: JBAS014612: Operation ("parallel-extension-add") failed - address: ([])
java.lang.RuntimeException: JBAS014670: Failed initializing module org.jboss.as.logging
	at org.jboss.as.controller.extension.ParallelExtensionAddHandler$1.execute(ParallelExtensionAddHandler.java:111)
	at org.jboss.as.controller.AbstractOperationContext.executeStep(AbstractOperationContext.java:660)
	at org.jboss.as.controller.AbstractOperationContext.doCompleteStep(AbstractOperationContext.java:501)
	at org.jboss.as.controller.AbstractOperationContext.completeStepInternal(AbstractOperationContext.java:298)
	at org.jboss.as.controller.AbstractOperationContext.executeOperation(AbstractOperationContext.java:293)
	at org.jboss.as.controller.ModelControllerImpl.boot(ModelControllerImpl.java:324)
	at org.jboss.as.controller.AbstractControllerService.boot(AbstractControllerService.java:297)
	at org.jboss.as.server.ServerService.boot(ServerService.java:356)
	at org.jboss.as.server.ServerService.boot(ServerService.java:331)
	at org.jboss.as.controller.AbstractControllerService$1.run(AbstractControllerService.java:259)
	at java.lang.Thread.run(Thread.java:745)
Caused by: java.util.concurrent.ExecutionException: java.lang.IllegalStateException: JBAS011592: The logging subsystem requires the log manager to be org.jboss.logmanager.LogManager. The subsystem has not be initialized and cannot be used. To use JBoss Log Manager you must add the system property "java.util.logging.manager" and set it to "org.jboss.logmanager.LogManager"
	at java.util.concurrent.FutureTask.report(FutureTask.java:122)
	at java.util.concurrent.FutureTask.get(FutureTask.java:192)
	at org.jboss.as.controller.extension.ParallelExtensionAddHandler$1.execute(ParallelExtensionAddHandler.java:103)
	... 10 more
Caused by: java.lang.IllegalStateException: JBAS011592: The logging subsystem requires the log manager to be org.jboss.logmanager.LogManager. The subsystem has not be initialized and cannot be used. To use JBoss Log Manager you must add the system property "java.util.logging.manager" and set it to "org.jboss.logmanager.LogManager"
	at org.jboss.as.logging.LoggingExtension.initialize(LoggingExtension.java:122)
	at org.jboss.as.controller.extension.ExtensionAddHandler.initializeExtension(ExtensionAddHandler.java:98)
	at org.jboss.as.controller.extension.ParallelExtensionAddHandler$ExtensionInitializeTask.call(ParallelExtensionAddHandler.java:139)
	at org.jboss.as.controller.extension.ParallelExtensionAddHandler$ExtensionInitializeTask.call(ParallelExtensionAddHandler.java:125)
	at java.util.concurrent.FutureTask.run(FutureTask.java:266)
	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
	at java.lang.Thread.run(Thread.java:745)
	at org.jboss.threads.JBossThread.run(JBossThread.java:122)

この対応で、以下の設定が入っています。

javaOptions ++= Seq("-Djava.util.logging.manager=org.jboss.logmanager.LogManager")  // embedded

普通は、maven-surefire-pluginで設定してって感じみたいですね。

Integration test with Arquillian and Wildfly
http://stackoverflow.com/questions/21851790/integration-test-with-arquillian-and-wildfly

あと、sbtの場合はforkしておく必要があります。

fork in Test := true

これは、Embeddedに限った話ではありませんが。

で、Managedと比べてどうなの?

正直、こういう使い方だと変わりがないような…Mavenを使ってWildFlyをダウンロードさせてテスト実行、みたいな使い方だと嬉しいんでしょうかねぇ?Managedに切り替えて動かしてみたりもしましたけど、特に速度が違うとかいうわけでもなさそうですし。

う〜ん、Managedを使えばいいかな?

参考)
Arquillian: Wildfly embedded?
http://stackoverflow.com/questions/20895332/arquillian-wildfly-embedded
JUnit/Arquillian: Run managed Wildfly 8.1 container
http://stackoverflow.com/questions/24615183/junit-arquillian-run-managed-wildfly-8-1-container

今回作成したコードは、こちらに置いています。
https://github.com/kazuhira-r/javaee7-scala-examples/tree/master/arquillian-wildfly-container-embedded