Kohei Nozaki's blog 

Entries tagged [test]

Mocking a unstable HTTP server with WireMock


Posted on Wednesday Feb 26, 2014 at 10:12AM in Technology


Environment

  • WireMock 1.4.3
  • HttpClient 4.3.2
  • Oracle JDK7u51

Why need it?

  • Sometimes website scraping stops at unexpected error like socket timeout or internal server error.
  • We prefer to retry several times at such occasion.
  • So we have to develop some retrying mechanism, and we have to create a test and create the mock of server.
    • So in this article, I'm going to try to create the mock with WireMock.

Requirements for client

  • Retry if processing fails, for 3 times.
  • Retry when timeout occurred.
    • SocketTimeoutException
  • Retry when server returned status code 5xx

Test cases

  1. Server returns code 200 without any problems.
  2. Server returns code 500 at first, then client will retry, server returns code 200.
  3. Server returns code 500 forever, then client will retry 3 times and give up.
  4. Server delays the response, then client will regard as timeout and retry, then server returns code 200.
  5. Server delays the response forever, then client will retry 3 times and give up.

Resources

pom.xml

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>org.nailedtothex</groupId>
    <artifactId>wiremock</artifactId>
    <version>0.0.1-SNAPSHOT</version>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <maven.compiler.source>1.7</maven.compiler.source>
        <maven.compiler.target>1.7</maven.compiler.target>
    </properties>

    <dependencies>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.11</version>
            <scope>test</scope>
        </dependency>
        <dependency>
          <groupId>org.hamcrest</groupId>
          <artifactId>hamcrest-core</artifactId>
          <version>1.3</version>
          <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>com.github.tomakehurst</groupId>
            <artifactId>wiremock</artifactId>
            <version>1.43</version>
            <classifier>standalone</classifier>
            <scope>test</scope>
            <exclusions>
                <exclusion>
                    <groupId>*</groupId>
                    <artifactId>*</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>org.apache.httpcomponents</groupId>
            <artifactId>httpclient</artifactId>
            <version>4.3.2</version>
        </dependency>
        <dependency>
            <groupId>org.apache.httpcomponents</groupId>
            <artifactId>fluent-hc</artifactId>
            <version>4.3.2</version>
        </dependency>
    </dependencies>

</project>

RetryableHttpFetcher.java

package org.nailedtothex.wiremock;

import java.io.IOException;
import java.util.logging.Level;
import java.util.logging.Logger;

import org.apache.http.HttpResponse;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.HttpRequestRetryHandler;
import org.apache.http.client.HttpResponseException;
import org.apache.http.client.ServiceUnavailableRetryStrategy;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.protocol.HttpContext;
import org.apache.http.util.EntityUtils;

public class RetryableHttpFetcher {

    private static final Logger log = Logger.getLogger(RetryableHttpFetcher.class.getName());

    // these parameters would be better to retrieve through JNDI or any other mechanism
    private int MAX_RETRIES = 3;
    private int RETRY_INTERVAL = 1000;
    private int TIMEOUT = 500;

    private final ServiceUnavailableRetryStrategy MY_SERVICE_UNAVAILABLE_RETRY_STRATEGY = new ServiceUnavailableRetryStrategy() {
        @Override
        public boolean retryRequest(HttpResponse response, int executionCount,
                org.apache.http.protocol.HttpContext context) {

            boolean rc = response.getStatusLine().getStatusCode() >= 500 && executionCount <= MAX_RETRIES;

            log.log(Level.INFO,
                    "retryRequest(): returning={0}, statusCode={1}, executionCount={2}, maxRetries={3}, interval={4}",
                    new Object[] { rc, response.getStatusLine().getStatusCode(), executionCount, MAX_RETRIES,
                            RETRY_INTERVAL });

            return rc;
        }

        @Override
        public long getRetryInterval() {
            return RETRY_INTERVAL;
        }
    };

    private final HttpRequestRetryHandler MY_HTTP_REQUEST_RETRY_HANDLER = new HttpRequestRetryHandler() {

        @Override
        public boolean retryRequest(IOException e, int executionCount, HttpContext context) {
            log.log(Level.INFO, "retryRequest(): exception={0}, executionCount={1}, maxRetries={2}",
                    new Object[] { e.getClass(), executionCount, MAX_RETRIES });

            if (executionCount > MAX_RETRIES) {
                log.log(Level.INFO, "give up: {0}", executionCount);
                return false;
            }

            if (e instanceof java.net.SocketTimeoutException) {
                log.log(Level.INFO, "retry: {0}", e.getMessage());
                return true;
            }

            log.log(Level.INFO, "not retry: {0}", e.getMessage());
            return false;
        }
    };

    private final RequestConfig MY_REQUEST_CONFIG = RequestConfig.custom()
            .setConnectionRequestTimeout(TIMEOUT)
            .setConnectTimeout(TIMEOUT)
            .setSocketTimeout(TIMEOUT)
            .build();

    public String fetchAsString(String url) throws ClientProtocolException, IOException {

        try (CloseableHttpClient client = HttpClientBuilder.create()
                .setDefaultRequestConfig(MY_REQUEST_CONFIG)
                .setRetryHandler(MY_HTTP_REQUEST_RETRY_HANDLER)
                .setServiceUnavailableRetryStrategy(MY_SERVICE_UNAVAILABLE_RETRY_STRATEGY)
                .build()) {

            try (CloseableHttpResponse res = client.execute(new HttpGet(url))) {
                if (res.getStatusLine().getStatusCode() >= 400) {
                    throw new HttpResponseException(res.getStatusLine().getStatusCode(), res.getStatusLine()
                            .getReasonPhrase());
                }
                return EntityUtils.toString(res.getEntity());
            }
        }

    }
}

RetryableHttpFetcherTest.java

package org.nailedtothex.wiremock;

import static com.github.tomakehurst.wiremock.client.WireMock.*;
import static org.hamcrest.CoreMatchers.*;
import static org.junit.Assert.*;

import java.net.SocketTimeoutException;

import org.apache.http.client.HttpResponseException;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;

import com.github.tomakehurst.wiremock.junit.WireMockRule;
import com.github.tomakehurst.wiremock.stubbing.Scenario;

public class RetryableHttpFetcherTest {

    @Rule
    public WireMockRule wireMockRule = new WireMockRule(18089);

    private RetryableHttpFetcher instance;

    @Before
    public void init() {
        instance = new RetryableHttpFetcher();
    }

    @Test
    public void test1_ok() throws Exception {
        stubFor(get(urlEqualTo("/hoge.txt")).willReturn(
                aResponse().withStatus(200).withHeader("Content-Type", "text/plain").withBody("OK")));
        String expected = "OK";

        String actual = instance.fetchAsString("http://localhost:18089/hoge.txt");

        assertThat(actual, is(expected));
    }

    @Test
    public void test2_retryAt500() throws Exception {
        stubFor(get(urlEqualTo("/500")).inScenario("retry at 500")
                .whenScenarioStateIs(Scenario.STARTED)
                .willSetStateTo("one time requested")
                .willReturn(aResponse().withBody("error").withStatus(500)));

        stubFor(get(urlEqualTo("/500")).inScenario("retry at 500")
                .whenScenarioStateIs("one time requested")
                .willReturn(aResponse().withBody("OK").withStatus(200)));

        String actual = instance.fetchAsString("http://localhost:18089/500");

        assertThat(actual, is("OK"));
    }

    @Test(expected = HttpResponseException.class)
    public void test3_retryAt500GiveUp() throws Exception {
        stubFor(get(urlEqualTo("/500"))
                .willReturn(aResponse().withBody("500").withStatus(500)));

        instance.fetchAsString("http://localhost:18089/500");
    }

    @Test
    public void test4_retryAtTimeout() throws Exception {
        stubFor(get(urlEqualTo("/timeout")).inScenario("retrying")
                .whenScenarioStateIs(Scenario.STARTED)
                .willSetStateTo("one time requested")
                .willReturn(aResponse().withBody("error").withStatus(500).withFixedDelay(3000)));
        stubFor(get(urlEqualTo("/timeout")).inScenario("retrying")
                .whenScenarioStateIs("one time requested")
                .willReturn(aResponse().withBody("OK").withStatus(200)));

        String actual = instance.fetchAsString("http://localhost:18089/timeout");
        assertThat(actual, is("OK"));
    }

    @Test(expected = SocketTimeoutException.class)
    public void test5_retryAtTimeoutGiveUp() throws Exception {
        stubFor(get(urlEqualTo("/timeout"))
                .willReturn(aResponse().withBody("timeout").withStatus(500).withFixedDelay(Integer.MAX_VALUE)));
        instance.fetchAsString("http://localhost:18089/timeout");
    }

    @Test(expected = HttpResponseException.class)
    public void notFound() throws Exception {
        instance.fetchAsString("http://localhost:18089/NOT_FOUND");
    }
}

Test logs

  • All tests were passed.

test1_ok

2 26, 2014 11:34:22 午前 org.nailedtothex.wiremock.RetryableHttpFetcher$1 retryRequest
情報: retryRequest(): returning=false, statusCode=200, executionCount=1, maxRetries=3, interval=1,000

test2_retryAt500

2 26, 2014 11:35:32 午前 org.nailedtothex.wiremock.RetryableHttpFetcher$1 retryRequest
情報: retryRequest(): returning=true, statusCode=500, executionCount=1, maxRetries=3, interval=1,000
2 26, 2014 11:35:33 午前 org.nailedtothex.wiremock.RetryableHttpFetcher$1 retryRequest
情報: retryRequest(): returning=false, statusCode=200, executionCount=2, maxRetries=3, interval=1,000

test3_retryAt500GiveUp

2 26, 2014 11:35:51 午前 org.nailedtothex.wiremock.RetryableHttpFetcher$1 retryRequest
情報: retryRequest(): returning=true, statusCode=500, executionCount=1, maxRetries=3, interval=1,000
2 26, 2014 11:35:52 午前 org.nailedtothex.wiremock.RetryableHttpFetcher$1 retryRequest
情報: retryRequest(): returning=true, statusCode=500, executionCount=2, maxRetries=3, interval=1,000
2 26, 2014 11:35:53 午前 org.nailedtothex.wiremock.RetryableHttpFetcher$1 retryRequest
情報: retryRequest(): returning=true, statusCode=500, executionCount=3, maxRetries=3, interval=1,000
2 26, 2014 11:35:54 午前 org.nailedtothex.wiremock.RetryableHttpFetcher$1 retryRequest
情報: retryRequest(): returning=false, statusCode=500, executionCount=4, maxRetries=3, interval=1,000

test4_retryAtTimeout

2 26, 2014 11:36:12 午前 org.nailedtothex.wiremock.RetryableHttpFetcher$2 retryRequest
情報: retryRequest(): exception=class java.net.SocketTimeoutException, executionCount=1, maxRetries=3
2 26, 2014 11:36:12 午前 org.nailedtothex.wiremock.RetryableHttpFetcher$2 retryRequest
情報: retry: Read timed out
2 26, 2014 11:36:12 午前 org.nailedtothex.wiremock.RetryableHttpFetcher$1 retryRequest
情報: retryRequest(): returning=false, statusCode=200, executionCount=1, maxRetries=3, interval=1,000

test5_retryAtTimeoutGiveUp

2 26, 2014 11:36:39 午前 org.nailedtothex.wiremock.RetryableHttpFetcher$2 retryRequest
情報: retryRequest(): exception=class java.net.SocketTimeoutException, executionCount=1, maxRetries=3
2 26, 2014 11:36:39 午前 org.nailedtothex.wiremock.RetryableHttpFetcher$2 retryRequest
情報: retry: Read timed out
2 26, 2014 11:36:40 午前 org.nailedtothex.wiremock.RetryableHttpFetcher$2 retryRequest
情報: retryRequest(): exception=class java.net.SocketTimeoutException, executionCount=2, maxRetries=3
2 26, 2014 11:36:40 午前 org.nailedtothex.wiremock.RetryableHttpFetcher$2 retryRequest
情報: retry: Read timed out
2 26, 2014 11:36:40 午前 org.nailedtothex.wiremock.RetryableHttpFetcher$2 retryRequest
情報: retryRequest(): exception=class java.net.SocketTimeoutException, executionCount=3, maxRetries=3
2 26, 2014 11:36:40 午前 org.nailedtothex.wiremock.RetryableHttpFetcher$2 retryRequest
情報: retry: Read timed out
2 26, 2014 11:36:41 午前 org.nailedtothex.wiremock.RetryableHttpFetcher$2 retryRequest
情報: retryRequest(): exception=class java.net.SocketTimeoutException, executionCount=4, maxRetries=3
2 26, 2014 11:36:41 午前 org.nailedtothex.wiremock.RetryableHttpFetcher$2 retryRequest
情報: give up: 4

References

  1. HttpClient 4 Cookbook


Mocking a HTTP server with WireMock


Posted on Saturday Feb 22, 2014 at 08:47AM in Technology


Environment

  • WireMock 1.4.3
  • HttpClient 4.2.3
  • Oracle JDK7u51

Resources

pom.xml

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>org.nailedtothex</groupId>
    <artifactId>wiremock</artifactId>
    <version>0.0.1-SNAPSHOT</version>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <maven.compiler.source>1.7</maven.compiler.source>
        <maven.compiler.target>1.7</maven.compiler.target>
    </properties>

    <dependencies>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.11</version>
            <scope>test</scope>
        </dependency>
        <dependency>
          <groupId>org.hamcrest</groupId>
          <artifactId>hamcrest-core</artifactId>
          <version>1.3</version>
          <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>com.github.tomakehurst</groupId>
            <artifactId>wiremock</artifactId>
            <version>1.43</version>
            <classifier>standalone</classifier>
            <scope>test</scope>
            <exclusions>
                <exclusion>
                    <groupId>*</groupId>
                    <artifactId>*</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>org.apache.httpcomponents</groupId>
            <artifactId>httpclient</artifactId>
            <version>4.3.2</version>
        </dependency>
        <dependency>
            <groupId>org.apache.httpcomponents</groupId>
            <artifactId>fluent-hc</artifactId>
            <version>4.3.2</version>
        </dependency>
    </dependencies>

</project>

HttpFetcher.java

package org.nailedtothex.wiremock;

import java.io.IOException;

import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.fluent.Request;

public class HttpFetcher {

    public String fetchAsString(String url) throws ClientProtocolException, IOException {
        return Request.Get(url).execute().returnContent().asString();
    }
}

HttpFetcherTest.java

package org.nailedtothex.wiremock;

import static com.github.tomakehurst.wiremock.client.WireMock.*;
import static org.hamcrest.CoreMatchers.*;
import static org.junit.Assert.*;

import org.apache.http.client.HttpResponseException;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;

import com.github.tomakehurst.wiremock.junit.WireMockRule;

public class HttpFetcherTest {

    @Rule
    public WireMockRule wireMockRule = new WireMockRule(18089);

    private HttpFetcher instance;

    @Before
    public void init() {
        instance = new HttpFetcher();
        stubFor(get(urlEqualTo("/hoge.txt")).willReturn(
                aResponse().withStatus(200).withHeader("Content-Type", "text/plain").withBody("hoge")));
        stubFor(get(urlEqualTo("/500.txt")).willReturn(
                aResponse().withStatus(500).withHeader("Content-Type", "text/plain").withBody("hoge")));
        stubFor(get(urlEqualTo("/503.txt")).willReturn(
                aResponse().withStatus(503).withHeader("Content-Type", "text/plain").withBody("hoge")));
    }

    @Test
    public void ok() throws Exception {
        String actual = instance.fetchAsString("http://localhost:18089/hoge.txt");
        String expected = "hoge";
        assertThat(actual, is(expected));
    }

    @Test(expected = HttpResponseException.class)
    public void notFound() throws Exception {
        instance.fetchAsString("http://localhost:18089/NOT_FOUND");
    }

    @Test(expected = HttpResponseException.class)
    public void internalServerError() throws Exception {
        instance.fetchAsString("http://localhost:18089/500.txt");
    }

    @Test(expected = HttpResponseException.class)
    public void serviceUnavailable() throws Exception {
        instance.fetchAsString("http://localhost:18089/503.txt");
    }
}

Remarks

  • To check behavior of the mock with browsers, we can set an breakpoint in the test class to prevent shutdown of the mock HTTP server.
    • I have checked that stub urls are working as I expected with firebug.
  • WireMock dependency brings many its dependencies, but I guess that are not necessary because it is classified as standalone, so I just add exclusions and it is working without any problems anyway.

References

  1. WireMock | WireMock
  2. Apache HttpComponents - Apache HttpComponents


Arquillian用ArchiveへクラスとMaven Dependencyを楽に追加する


Posted on Tuesday Jan 28, 2014 at 09:48PM in Technology


環境

  • shrinkwrap-resolver 2.1.0-alpha-1
  • wildfly-arquillian-container-remote 8.0.0.CR1
  • Arquillian 1.1.2.Final
  • WildFly 8.0.0.CR1
  • Eclipse Kepler SR1
  • Oracle JDK7u51

サブパッケージを再帰的に探索させてクラスを追加する

例えばこういう状況で

テストするのに必要なクラス群が複数のパッケージに分散している

クラスの配置

Hige.java

package org.example.hige;

import javax.inject.Named;

@Named
public class Hige {

    public String hige() {
        return "hige()";
    }
}

Hoge.java

package org.example.hoge;

import javax.inject.Inject;
import javax.inject.Named;

import org.example.hige.Hige;

@Named
public class Hoge {

    @Inject
    Hige hige;

    public String hoge() {
        return "hoge()" + hige.hige();
    }
}

こうするとよい

Archive作成時にaddPackages()メソッドをこういう感じで呼んでやればよい。これでいちいち全パッケージに対してaddPackage()呼んでやらずにすむ。第一引数がtrueだと、第二引数で与えたパッケージのサブパッケージを再帰的に探索してくれる。

テストクラス

package org.example.hoge;

import javax.inject.Inject;

import org.jboss.arquillian.container.test.api.Deployment;
import org.jboss.arquillian.junit.Arquillian;
import org.jboss.shrinkwrap.api.Archive;
import org.jboss.shrinkwrap.api.ShrinkWrap;
import org.jboss.shrinkwrap.api.asset.EmptyAsset;
import org.jboss.shrinkwrap.api.spec.WebArchive;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;

@RunWith(Arquillian.class)
public class HogeTest {

    @Deployment
    public static Archive<?> createDeployment() {
        WebArchive a =
                ShrinkWrap.create(WebArchive.class, "test.war")
                .addAsWebInfResource(EmptyAsset.INSTANCE, "beans.xml")
                .addPackages(true, "org.example");
        return a;
    }

    @Inject
    Hoge hoge;

    @Test
    public void test() throws Exception {
        Assert.assertEquals("hoge()hige()", hoge.hoge());
    }
}

Maven Dependencyを楽に追加する

例えばこういう状況で

guavaをpom.xmlに設定して使っている。Archiveを作る際に、いちいち依存するjarファイルをaddLibraryしていくのが面倒くさい。

Hoge.java

package org.example.hoge;

import javax.inject.Named;

import com.google.common.collect.Lists;

@Named
public class Hoge {

    public void hoge() {
        Lists.newArrayList();
    }
}

こうするとよい

pom.xml

shrinkwrap-resolverへの依存を追加する(groupIdがorg.jboss.shrinkwrap.resolverの要素)。例えばこんな感じ

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.example</groupId>
    <artifactId>arquillian-prac</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>war</packaging>

    <properties>
        <maven.compiler.source>1.7</maven.compiler.source>
        <maven.compiler.target>1.7</maven.compiler.target>
        <failOnMissingWebXml>false</failOnMissingWebXml>
    </properties>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.jboss.shrinkwrap.resolver</groupId>
                <artifactId>shrinkwrap-resolver-bom</artifactId>
                <version>2.1.0-alpha-1</version>
                <scope>import</scope>
                <type>pom</type>
            </dependency>
            <dependency>
                <groupId>org.jboss.arquillian</groupId>
                <artifactId>arquillian-bom</artifactId>
                <version>1.1.2.Final</version>
                <scope>import</scope>
                <type>pom</type>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>2.3.2</version>
                <configuration>
                    <source>1.7</source>
                    <target>1.7</target>
                </configuration>
            </plugin>
            <plugin>
                <artifactId>maven-surefire-plugin</artifactId>
                <version>2.12</version>
            </plugin>
        </plugins>
    </build>

    <dependencies>
        <dependency>
            <groupId>javax</groupId>
            <artifactId>javaee-api</artifactId>
            <version>7.0</version>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.11</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.mockito</groupId>
            <artifactId>mockito-all</artifactId>
            <version>1.9.5</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.hamcrest</groupId>
            <artifactId>hamcrest-integration</artifactId>
            <version>1.2.1</version>
        </dependency>
        <dependency>
            <groupId>net.avh4.util</groupId>
            <artifactId>imagecomparison</artifactId>
            <version>0.0.2</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.jboss.arquillian.junit</groupId>
            <artifactId>arquillian-junit-container</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.wildfly</groupId>
            <artifactId>wildfly-arquillian-container-remote</artifactId>
            <version>8.0.0.CR1</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.jboss.arquillian.extension</groupId>
            <artifactId>arquillian-persistence-impl</artifactId>
            <version>1.0.0.Alpha6</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.jboss.shrinkwrap.resolver</groupId>
            <artifactId>shrinkwrap-resolver-impl-maven</artifactId>
            <version>2.1.0-alpha-1</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.jboss.shrinkwrap.resolver</groupId>
            <artifactId>shrinkwrap-resolver-api-maven</artifactId>
            <version>2.1.0-alpha-1</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>com.google.guava</groupId>
            <artifactId>guava</artifactId>
            <version>16.0</version>
        </dependency>
    </dependencies>
</project>

テストクラス

package org.example.hoge;

import javax.inject.Inject;

import org.jboss.arquillian.container.test.api.Deployment;
import org.jboss.arquillian.junit.Arquillian;
import org.jboss.shrinkwrap.api.Archive;
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.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;

@RunWith(Arquillian.class)
public class HogeTest {

    @Deployment
    public static Archive<?> createDeployment() {
        WebArchive a =
                ShrinkWrap.create(WebArchive.class, "test.war")
                .addAsWebInfResource(EmptyAsset.INSTANCE, "beans.xml")
                .addPackages(true, "org.example");

        a.addAsLibraries(
                Maven.resolver()
                .loadPomFromFile("pom.xml")
                .importRuntimeDependencies()
                .resolve()
                .withTransitivity()
                .asFile());

        return a;
    }

    @Inject
    Hoge hoge;

    @Test
    public void test() throws Exception {
        hoge.hoge();
        Assert.assertTrue(true);
    }
}

importRuntimeDependencies()の他にもいろいろあるので必要に応じて使い分ける。ただ多くなりすぎるとデプロイが遅くなる。

  • importRuntimeAndCompileDependencies()
  • importDependencies(ScopeType…)
  • importRuntimeAndTestDependencies()
  • importRuntimeDependencies()
  • importTestDependencies()

こういう感じにすると依存性を個別に追加できる[3]

        File[] files = Maven.resolver()
                .loadPomFromFile("pom.xml")
                .resolve("org.apache.commons:commons-lang3:3.2.1")
                .withTransitivity()
                .asFile();

        WebArchive w = ShrinkWrap.create(WebArchive.class, "hoge.war").addClass(HogeBean.class)
                .addAsManifestResource(EmptyAsset.INSTANCE, "beans.xml").addAsLibraries(files);

バージョンの省略も可。省略するとpom.xmlに書いてあるバージョンを取ってきてくれる

        File[] files = Maven.resolver()
                .loadPomFromFile("pom.xml")
                .resolve("org.apache.commons:commons-lang3")
                .withTransitivity()
                .asFile();

参考文献

  1. Add all classes in src/main/java | Community
  2. java - Adding all Maven dependencies to Arquillian - Stack Overflow
  3. shrinkwrap/resolver · GitHub
  4. Using the ShrinkWrap Maven Resolver for Arquillian Tests | Develop In Java
  5. arquillian 1.1.0 final and shrinkwrap | Community
  6. Add directory recursively to arquillian using S… | Community
  7. Add src/main/java to webarchive | Community


Arquillian Persistence ExtensionでExcelデータを使ってみる


Posted on Tuesday Jan 28, 2014 at 04:21PM in Technology


Excelで作ったデータを投入したり検証に使ったりしてみます

環境

  • Arquillian Persistence Extension 1.0.0.Alpha6
  • wildfly-arquillian-container-remote 8.0.0.CR1
  • Arquillian 1.1.2.Final
  • Hibernate 4.3.0.Final
  • WildFly 8.0.0.CR1
  • Eclipse Kepler SR1
  • Oracle JDK7u51
  • postgresql-9.3-1100.jdbc41.jar
  • PostgreSQL 9.2.4
  • Excel 2011

前提条件

準備

資源の配置図

下図で選択した資源を作成または編集します

VariousTypes.java

package org.arquillian.example;

import java.io.Serializable;
import java.math.BigDecimal;
import java.util.Date;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Temporal;
import javax.persistence.TemporalType;

@Entity
public class VariousTypes implements Serializable {

    private static final long serialVersionUID = 1L;
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long bigint1;
    @Column
    private String varchar1;
    @Column
    @Temporal(TemporalType.DATE)
    private Date date1;
    @Column
    @Temporal(TemporalType.TIME)
    private Date time1;
    @Column
    @Temporal(TemporalType.TIMESTAMP)
    private Date timestamp1;
    @Column(precision = 7, scale = 4)
    private BigDecimal numeric1;
    @Column
    private Double double1;

    public Long getBigint1() {
        return bigint1;
    }

    public void setBigint1(Long bigint1) {
        this.bigint1 = bigint1;
    }

    public String getVarchar1() {
        return varchar1;
    }

    public void setVarchar1(String varchar1) {
        this.varchar1 = varchar1;
    }

    public Date getDate1() {
        return date1;
    }

    public void setDate1(Date date1) {
        this.date1 = date1;
    }

    public Date getTime1() {
        return time1;
    }

    public void setTime1(Date time1) {
        this.time1 = time1;
    }

    public Date getTimestamp1() {
        return timestamp1;
    }

    public void setTimestamp1(Date timestamp1) {
        this.timestamp1 = timestamp1;
    }

    public BigDecimal getNumeric1() {
        return numeric1;
    }

    public void setNumeric1(BigDecimal numeric1) {
        this.numeric1 = numeric1;
    }

    public Double getDouble1() {
        return double1;
    }

    public void setDouble1(Double double1) {
        this.double1 = double1;
    }

    @Override
    public String toString() {
        return "VariousTypes [bigint1=" + bigint1 + ", varchar1=" + varchar1 + ", date1=" + date1 + ", time1=" + time1
                + ", timestamp1=" + timestamp1 + ", numeric1=" + numeric1 + ", double1=" + double1 + "]";
    }

}

orm.xml

以下のようなクエリを追加します

    <named-query name="findVariousTypes">
        <query><![CDATA[
            SELECT
                v
            FROM
                VariousTypes v
        ]]></query>
    </named-query>

XlsDataSetTest.java

package org.arquillian.example;

import java.math.BigDecimal;
import java.util.Calendar;
import java.util.Date;
import java.util.List;

import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.transaction.Transactional;

import org.jboss.arquillian.container.test.api.Deployment;
import org.jboss.arquillian.junit.Arquillian;
import org.jboss.arquillian.persistence.ShouldMatchDataSet;
import org.jboss.arquillian.persistence.UsingDataSet;
import org.jboss.shrinkwrap.api.Archive;
import org.jboss.shrinkwrap.api.ShrinkWrap;
import org.jboss.shrinkwrap.api.asset.EmptyAsset;
import org.jboss.shrinkwrap.api.spec.WebArchive;
import org.junit.Test;
import org.junit.runner.RunWith;


@RunWith(Arquillian.class)
public class XlsDataSetTest {
   @Deployment
   public static Archive<?> createDeployment() {
       Archive<?> a = ShrinkWrap.create(WebArchive.class, "test.war")
           .addPackage(VariousTypes.class.getPackage())
           .addAsResource("test-persistence.xml", "META-INF/persistence.xml")
           .addAsResource("META-INF/orm.xml", "META-INF/orm.xml")
           .addAsWebInfResource(EmptyAsset.INSTANCE, "beans.xml")
           .addAsWebInfResource("jbossas-ds.xml");
       return a;
   }

   @PersistenceContext
   EntityManager em;

   @Test
   @Transactional
   @UsingDataSet("datasets/xlsDataSet/variousTypes.xls")
   @ShouldMatchDataSet(value = "datasets/xlsDataSet/variousTypesExpected.xls", orderBy = "bigint1")
   public void select() throws Exception {
       dumpEntityList(em.createNamedQuery("findVariousTypes", VariousTypes.class).getResultList());

       Calendar cal = Calendar.getInstance();
       cal.set(2014, 0, 2, 12, 13, 14);
       cal.set(Calendar.MILLISECOND, 0);
       Date newDate = cal.getTime();

       VariousTypes toModify = em.find(VariousTypes.class, 1l);
       toModify.setVarchar1("HogeHoge");
       toModify.setDate1(newDate);
       toModify.setTime1(newDate);
       toModify.setTimestamp1(newDate);
       toModify.setNumeric1(new BigDecimal("222.2222"));
       toModify.setDouble1(1.0);

       dumpEntityList(em.createNamedQuery("findVariousTypes", VariousTypes.class).getResultList());
   }

   protected void dumpEntityList(List<?> list){
       for(Object o : list){
           System.out.println(o + ", contains=" + em.contains(o));
       }
   }
}

variousTypes.xls

今回はMacのExcel2011で作りましたが、.xls形式(.xlsx形式でなく)で出力していれば、LibreOfficeやOpenOfficeで作ってもおそらく問題ありません。日付や時刻は文字列型で入れます。普通に入力するとシリアル値になって正常に認識してくれなくなります。こんな感じ。

blobは少し調べた範囲ではExcel形式では対応してなさそうなのでスルーします。XML形式なら対応しているそうですが試してません[2]

variousTypesExpected.xls

こんな感じ。IDが1のデータをテストクラス内で更新してその結果を検証してみます

テスト実行

投入データの確認

デバッガで45行目で止めて投入データを見てみます

テーブル

jpaprac=# \d varioustypes
             Table "public.varioustypes"
   Column   |            Type             | Modifiers 
------------+-----------------------------+-----------
 bigint1    | bigint                      | not null
 date1      | date                        | 
 double1    | double precision            | 
 numeric1   | numeric(7,4)                | 
 time1      | time without time zone      | 
 timestamp1 | timestamp without time zone | 
 varchar1   | character varying(255)      | 
Indexes:
    "varioustypes_pkey" PRIMARY KEY, btree (bigint1)

jpaprac=# 

データ

jpaprac=# select bigint1, varchar1, date1, time1, timestamp1, numeric1, double1 from varioustypes ;
 bigint1 | varchar1 |   date1    |  time1   |     timestamp1      | numeric1 | double1 
---------+----------+------------+----------+---------------------+----------+---------
       1 | Hoge     | 1980-01-01 | 12:00:00 | 1980-01-01 12:00:00 | 100.0001 |     0.5
       2 | Hige     | 1980-01-02 | 12:00:01 | 1980-01-02 12:00:01 | 100.0020 |    0.25
       3 | Fuge     | 1980-01-03 | 12:00:02 | 1980-01-03 12:00:02 | 100.0300 |   0.125
(3 rows)

jpaprac=# 

ちゃんと入ってますね

実行結果

JUnit窓

コンソール出力

18:21:16,667 INFO  [stdout] (pool-2-thread-20) Hibernate: select varioustyp0_.bigint1 as bigint1_2_, varioustyp0_.date1 as date2_2_, varioustyp0_.double1 as double3_2_, varioustyp0_.numeric1 as numeric4_2_, varioustyp0_.time1 as time5_2_, varioustyp0_.timestamp1 as timestam6_2_, varioustyp0_.varchar1 as varchar7_2_ from VariousTypes varioustyp0_
18:21:16,670 INFO  [stdout] (pool-2-thread-20) VariousTypes [bigint1=1, varchar1=Hoge, date1=1980-01-01, time1=12:00:00, timestamp1=1980-01-01 12:00:00.0, numeric1=100.0001, double1=0.5], contains=true
18:21:16,670 INFO  [stdout] (pool-2-thread-20) VariousTypes [bigint1=2, varchar1=Hige, date1=1980-01-02, time1=12:00:01, timestamp1=1980-01-02 12:00:01.0, numeric1=100.0020, double1=0.25], contains=true
18:21:16,671 INFO  [stdout] (pool-2-thread-20) VariousTypes [bigint1=3, varchar1=Fuge, date1=1980-01-03, time1=12:00:02, timestamp1=1980-01-03 12:00:02.0, numeric1=100.0300, double1=0.125], contains=true
18:21:16,676 INFO  [stdout] (pool-2-thread-20) Hibernate: update VariousTypes set date1=?, double1=?, numeric1=?, time1=?, timestamp1=?, varchar1=? where bigint1=?
18:21:16,677 INFO  [stdout] (pool-2-thread-20) Hibernate: select varioustyp0_.bigint1 as bigint1_2_, varioustyp0_.date1 as date2_2_, varioustyp0_.double1 as double3_2_, varioustyp0_.numeric1 as numeric4_2_, varioustyp0_.time1 as time5_2_, varioustyp0_.timestamp1 as timestam6_2_, varioustyp0_.varchar1 as varchar7_2_ from VariousTypes varioustyp0_
18:21:16,678 INFO  [stdout] (pool-2-thread-20) VariousTypes [bigint1=2, varchar1=Hige, date1=1980-01-02, time1=12:00:01, timestamp1=1980-01-02 12:00:01.0, numeric1=100.0020, double1=0.25], contains=true
18:21:16,678 INFO  [stdout] (pool-2-thread-20) VariousTypes [bigint1=3, varchar1=Fuge, date1=1980-01-03, time1=12:00:02, timestamp1=1980-01-03 12:00:02.0, numeric1=100.0300, double1=0.125], contains=true
18:21:16,681 INFO  [stdout] (pool-2-thread-20) VariousTypes [bigint1=1, varchar1=HogeHoge, date1=Thu Jan 02 12:13:14 JST 2014, time1=Thu Jan 02 12:13:14 JST 2014, timestamp1=Thu Jan 02 12:13:14 JST 2014, numeric1=222.2222, double1=1.0], contains=true

更新後のDBも覗いてみたいところですが、コンソールにJPQLから覗いた結果が出ているのでそれでよしとします

備考

  • 浮動小数点の検証が気になる。丸め誤差とか。最悪テスト実行後に走らせるSQL文にUPDATEかまして端数を切り捨てるとかすれば出来そうだけど気持ち悪いなあ
  • xlsのテストデータ修正後はEclipseでRefreshかけないとなぜか更新後のデータを読んでくれない

参考文献

  1. Java/DBUnit/ExcelシートでImport,Exportする - きのさいと
  2. 6. 便利な機能 | TECHSCORE(テックスコア)


ArquillianのテストをリモートのWildFly内でデバッグしてみる


Posted on Saturday Jan 25, 2014 at 10:37PM in Technology


ArquillianをWildFly8.0.0.CR1で動かしてみるの続きです。テストクラスのデバッグをしてみます

ArquillianのRemoteでも普通のクラス同様にデバッグできます[1]。今回のようなケースではEdit Source Lookup Pathをやらないといけないのでその手順を残しておきます

環境

  • wildfly-arquillian-container-remote 8.0.0.CR1
  • Arquillian 1.1.2.Final
  • WildFly 8.0.0.CR1
  • Eclipse Kepler SR1
  • Oracle JDK7u51

前提条件

やってみる

  1. 資源は前述の前提条件の記事で書いたのと全く同じ状態で、テストクラスの39行目にブレークポイントを設定します

  2. WildFlyをDebugで起動します

  3. 右クリック→Run As→JUnit Test。基本的にはこの手順で普通にブレークポイントで止まってデバッグ出来るはず。この手順より後はソースがデバッガに認識されない場合の設定手順です

  4. OK。コンパイラオプションどうのこうの言ってるけど、これONになってても出るし

  5. Yes

  6. Debugパースペクティブに切り替わる。Source not foundと出たら、画面中ほどのEdit Source Lookup Pathをクリック

  7. Addをクリック

  8. Workspace Folderを選択してOK

  9. 作業中のプロジェクトのsrc/test/javaを選択してOK

  10. 追加されたのを確認してOK

  11. ソースと止まってるところが表示されるのを確認

  12. いったんResume(F8)でデバッガを終わらせる

  13. この作業をしている間にデバッガが勝手に終了していたら、投入されたまま放ったらかされているテストデータの掃除などをしておく

  14. 3の手順で再度テストを実行

参考文献

  1. Getting Started: Rinse and Repeat · Arquillian Guides