CLOVER🍀

That was when it all began.

ArquillianとScalaTestを合わせて使う

ArquillianとScalaTestを合わせて使う、最初にArquillianを使った時に思っていたことですが、最初からそんなことするとハマりそうだと思ってやめていました。

で、それぞれを別個に使ってきて、合わせられそうな気がしたので、今回トライ。これで、個人的にはArquillianネタは一段落かと思っています。

依存関係の定義とテスト対象

まずは、ビルドの依存関係。
build.sbt

name := "arquillian-scalatest-integration"

version := "0.0.1-SNAPSHOT"

scalaVersion := "2.10.3"

organization := "littlewings"

resolvers += "Public JBoss Group" at "http://repository.jboss.org/nexus/content/groups/public-jboss"

fork in Test := true

libraryDependencies ++= Seq(
  "org.jboss.spec" % "jboss-javaee-web-6.0" % "3.0.2.Final" % "provided",
  "org.jboss.as" % "jboss-as-arquillian-container-remote" % "7.1.1.Final" % "test" excludeAll(
    ExclusionRule(organization = "org.jboss.shrinkwrap"),
    ExclusionRule(organization = "org.jboss.shrinkwrap.descriptors")
  ),
  "org.jboss.arquillian.junit" % "arquillian-junit-container" % "1.1.2.Final" % "test" excludeAll(
    ExclusionRule(organization = "org.jboss.shrinkwrap"),
    ExclusionRule(organization = "org.jboss.shrinkwrap.descriptors")
  ),
  "org.jboss.arquillian.protocol" % "arquillian-protocol-servlet" % "1.1.2.Final" % "test",
  "org.jboss.shrinkwrap" % "shrinkwrap-api" % "1.0.0-cr-1" % "test",
  "org.jboss.shrinkwrap" % "shrinkwrap-impl-base" % "1.1.2" % "test",
  "org.jboss.shrinkwrap.descriptors" % "shrinkwrap-descriptors-spi" % "2.0.0-alpha-3" % "test",
  "org.jboss.shrinkwrap.resolver" % "shrinkwrap-resolver-depchain" % "2.0.1" % "test" excludeAll(
    ExclusionRule(organization = "org.jboss.shrinkwrap")
  ),
  "org.jboss.resteasy" % "resteasy-jaxrs" % "2.3.2.Final" % "test",
  "org.scalatest" %% "scalatest" % "2.0" % "test"
)

1番下に、ScalaTestを入れています。また、Arquillianを使ったテストはRemoteモードとします。

続いて、テスト対象のコードを用意。

EJB
src/main/scala/javaee6/web/service/CalcService.scala

package javaee6.web.service

import javax.ejb.{LocalBean, Stateless}

@Stateless
@LocalBean
class CalcService {
  def add(left: Int, right: Int): Int =
    left + right

  def multiply(left: Int, right: Int): Int =
    left * right
}

JAX-RS
src/main/scala/javaee6/web/rest/RestApplication.scala

package javaee6.web.rest

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

@ApplicationPath("/rest")
class RestApplication extends Application

src/main/scala/javaee6/web/rest/CalcResource.scala

package javaee6.web.rest

import javax.ws.rs.{GET, Path, Produces, QueryParam}
import javax.ws.rs.core.MediaType

@Path("calc")
class CalcResource {
  @GET
  @Path("add")
  @Produces(Array(MediaType.TEXT_PLAIN))
  def add(@QueryParam("p1") p1: Int, @QueryParam("p2") p2: Int): String =
    (p1 + p2).toString

  @GET
  @Path("multiply")
  @Produces(Array(MediaType.TEXT_PLAIN))
  def multiply(@QueryParam("p1") p1: Int, @QueryParam("p2") p2: Int): String =
    (p1 * p2).toString
}

いずれも、単純なクラスですね。

beans.xmlも作成。

src/main/webapp/WEB-INF/beans.xml

では、続いてテストコードに移りましょう。

ScalaTestとJUnitを合わせて使う

Arquillianは、JUnitのRunnerと@InjectなどのCDIに関する処理を行うため、ScalaTestを使うといっても@Testアノテーションを使ったJUnitベースの書き方をする必要があると考えています。なので、ScalaTestのJUnit統合クラスを使用。

Getting started with JUnit 4 and Scala
http://www.scalatest.org/getting_started_with_junit_4_in_scala

テストコード自体は、ScalaTestのMatcherを使いたいので、以下のクラスをテストクラスは継承することにします。

import org.scalatest.junit.JUnitSuite

参考)
一応、FunSuiteでもJUnit Runnerは使えるみたいなのですが

Using JUnitRunner
http://www.scalatest.org/user_guide/using_junit_runner

やっぱりこのスタイルだと、@Injectアノテーションがうまく動きませんでした。そりゃあ、そうですよね。

では、まずはEJBのテストコード。
src/test/scala/javaee6/web/service/CalcServiceTest.scala

package javaee6.web.service

import java.io.File
import javax.inject.Inject

import org.jboss.arquillian.container.test.api.Deployment
import org.jboss.arquillian.junit.Arquillian
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

import org.scalatest.Matchers._
import org.scalatest.junit.JUnitSuite

import org.junit.Test
import org.junit.runner.RunWith

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

  @Test
  def addTest: Unit = {
    calcService.add(1, 2) should be (3)
    calcService.add(5, 5) should be (10)
  }

  @Test
  def multiplyTest: Unit = {
    calcService.multiply(1, 2) should be (2)
    calcService.multiply(5, 5) should be (25)
  }
}

object CalcServiceTest {
  @Deployment
  def createDeployment: WebArchive = {
    val depLibs: (String*) => Seq[File] = artifacts =>
      artifacts.map { artifact =>
        Maven
          .resolver
          .resolve(artifact)
          .withTransitivity
          .asFile
      }.flatten

    ShrinkWrap
      .create(classOf[WebArchive], "arquillian-test.war")
      .addPackages(true, classOf[CalcService].getPackage)
      .addAsWebInfResource(EmptyAsset.INSTANCE, "beans.xml")
      .addAsLibraries {
        depLibs("org.scala-lang:scala-library:2.10.3",
                "org.scalatest:scalatest_2.10:2.0"): _*
      }
  }
}

ScalaTestをJUnitを使ったスタイルで書くので、JUnitSuiteクラスを継承。

@RunWith(classOf[Arquillian])
class CalcServiceTest extends JUnitSuite {

もちろん、RunnerにArquillianも指定して。

これで、JUnitの@Testアノテーションを使ってテストコードを書きます。

  @Test
  def addTest: Unit = {

値の検証自体は、ScalaTestのMatcherで行います。

    calcService.add(1, 2) should be (3)
    calcService.add(5, 5) should be (10)

あと、余談ですが、デプロイするWARにはScalaTestも入れる必要があったみたいなので、Scala本体と合わせてこんな感じにしました。

  @Deployment
  def createDeployment: WebArchive = {
    val depLibs: (String*) => Seq[File] = artifacts =>
      artifacts.map { artifact =>
        Maven
          .resolver
          .resolve(artifact)
          .withTransitivity
          .asFile
      }.flatten

    ShrinkWrap
      .create(classOf[WebArchive], "arquillian-test.war")
      .addPackages(true, classOf[CalcService].getPackage)
      .addAsWebInfResource(EmptyAsset.INSTANCE, "beans.xml")
      .addAsLibraries {
        depLibs("org.scala-lang:scala-library:2.10.3",
                "org.scalatest:scalatest_2.10:2.0"): _*
      }
  }

続いて、JAX-RSのテストコード。
src/test/scala/javaee6/web/rest/CalcResourceTest.scala

package javaee6.web.rest

import java.io.File
import java.net.URL

import javax.ws.rs.ApplicationPath
import javax.ws.rs.core.{MediaType, Response}

import org.jboss.arquillian.container.test.api.{Deployment, RunAsClient}
import org.jboss.arquillian.junit.Arquillian
import org.jboss.arquillian.test.api.ArquillianResource

import org.jboss.resteasy.client.ClientRequest

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

import org.scalatest.Matchers._
import org.scalatest.junit.JUnitSuite

import org.junit.Test
import org.junit.runner.RunWith

@RunWith(classOf[Arquillian])
@RunAsClient
class CalcResourceTest extends JUnitSuite {
  private val resourcePrefix: String =
    classOf[RestApplication]
      .getAnnotation(classOf[ApplicationPath])
      .value
      .substring(1)

  @ArquillianResource
  private var deploymentUrl: URL = _

  @Test
  def addTest: Unit = {
    val request =
      new ClientRequest(s"${deploymentUrl}${resourcePrefix}/calc/add?p1=1&p2=2")
    request.header("Accept", MediaType.TEXT_PLAIN)

    val response = request.get(classOf[String])

    response.getStatus should be (Response.Status.OK.getStatusCode)
    response.getEntity should be ("3")
  }

  @Test
  def multiplyTest: Unit = {
    val request =
      new ClientRequest(s"${deploymentUrl}${resourcePrefix}/calc/multiply?p1=1&p2=2")
    request.header("Accept", MediaType.TEXT_PLAIN)

    val response = request.get(classOf[String])

    response.getStatus should be (Response.Status.OK.getStatusCode)
    response.getEntity should be ("2")
  }
}

object CalcResourceTest {
  @Deployment
  def createDeployment: WebArchive = {
    val depLibs: (String*) => Seq[File] = artifacts =>
      artifacts.map { artifact =>
        Maven
          .resolver
          .resolve(artifact)
          .withTransitivity
          .asFile
      }.flatten

    ShrinkWrap
      .create(classOf[WebArchive], "arquillian-test.war")
      .addPackages(true, classOf[CalcResource].getPackage)
      .addAsWebInfResource(EmptyAsset.INSTANCE, "beans.xml")
      .addAsLibraries {
        depLibs("org.scala-lang:scala-library:2.10.3",
                "org.scalatest:scalatest_2.10:2.0"): _*
      }
  }
}

最後に、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">

  <defaultProtocol type="Servlet 3.0"/>

</arquillian>

JBoss ASを起動して

jboss-as-7.1.1.Final/bin/standalone.sh

テスト〜。

> test

テストケース、4つパス。OKですね。

> test
[info] CalcResourceTest:
12 08, 2013 11:07:53 午後 org.xnio.Xnio <clinit>
INFO: XNIO Version 3.0.3.GA
12 08, 2013 11:07:53 午後 org.xnio.nio.NioXnio <clinit>
INFO: XNIO NIO Implementation Version 3.0.3.GA
12 08, 2013 11:07:53 午後 org.jboss.remoting3.EndpointImpl <clinit>
INFO: JBoss Remoting version 3.2.3.GA
SLF4J: Failed to load class "org.slf4j.impl.StaticLoggerBinder".
SLF4J: Defaulting to no-operation (NOP) logger implementation
SLF4J: See http://www.slf4j.org/codes.html#StaticLoggerBinder for further details.
[info] - multiplyTest
[info] - addTest
[info] CalcServiceTest:
[info] - multiplyTest
[info] - addTest
[info] Run completed in 26 seconds, 694 milliseconds.
[info] Total number of tests run: 4
[info] Suites: completed 2, aborted 0
[info] Tests: succeeded 4, failed 0, canceled 0, ignored 0, pending 0
[info] All tests passed.
[success] Total time: 27 s, completed 2013/12/08 23:08:15

こんな感じで。今回のコードは、こちらに置いておきました。

https://github.com/kazuhira-r/javaee6-scala-examples/tree/master/arquillian-scalatest-integration