Entries tagged [test]
Mocking a unstable HTTP server with WireMock
TweetPosted 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
- Server returns code 200 without any problems.
- Server returns code 500 at first, then client will retry, server returns code 200.
- Server returns code 500 forever, then client will retry 3 times and give up.
- Server delays the response, then client will regard as timeout and retry, then server returns code 200.
- 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
Tags: test
Mocking a HTTP server with WireMock
TweetPosted 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
Tags: test
Arquillian用ArchiveへクラスとMaven Dependencyを楽に追加する
TweetPosted 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();
参考文献
- Add all classes in src/main/java | Community
- java - Adding all Maven dependencies to Arquillian - Stack Overflow
- shrinkwrap/resolver · GitHub
- Using the ShrinkWrap Maven Resolver for Arquillian Tests | Develop In Java
- arquillian 1.1.0 final and shrinkwrap | Community
- Add directory recursively to arquillian using S… | Community
- Add src/main/java to webarchive | Community
Tags: test
Arquillian Persistence ExtensionでExcelデータを使ってみる
TweetPosted 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
前提条件
- NativeQueryでSELECTしてみるで使った資源と環境を流用します
準備
資源の配置図
下図で選択した資源を作成または編集します
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かけないとなぜか更新後のデータを読んでくれない
参考文献
Tags: test
ArquillianのテストをリモートのWildFly内でデバッグしてみる
TweetPosted 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
前提条件
- NativeQueryでSELECTしてみるで使った資源と環境を流用します
やってみる
資源は前述の前提条件の記事で書いたのと全く同じ状態で、テストクラスの39行目にブレークポイントを設定します
WildFlyをDebugで起動します
右クリック→Run As→JUnit Test。基本的にはこの手順で普通にブレークポイントで止まってデバッグ出来るはず。この手順より後はソースがデバッガに認識されない場合の設定手順です
OK。コンパイラオプションどうのこうの言ってるけど、これONになってても出るし
Yes
Debugパースペクティブに切り替わる。Source not foundと出たら、画面中ほどのEdit Source Lookup Pathをクリック
Addをクリック
Workspace Folderを選択してOK
作業中のプロジェクトのsrc/test/javaを選択してOK
追加されたのを確認してOK
ソースと止まってるところが表示されるのを確認
いったんResume(F8)でデバッガを終わらせる
この作業をしている間にデバッガが勝手に終了していたら、投入されたまま放ったらかされているテストデータの掃除などをしておく
3の手順で再度テストを実行
参考文献
Tags: test