dropwizard-testingが便利でした


Dropwizardでテストする際、```dropwizard-testing```を使うといくらか便利だったので記述しておきます。

# maven設定

```pom.xml
    <dependency>
      <groupId>io.dropwizard</groupId>
      <artifactId>dropwizard-testing</artifactId>
      <version>${dropwizard.version}</version>
      <scope>test</scope>
    </dependency>
```

# Unit Test

## Representationクラスのテスト

RepresentationはJacksonで変換対象になるクラスになります。

テスト対象のクラスはこちらです。

```Saying.java
public class Saying {
    private long id;

    @Length(max = 3)
    private String content;

    public Saying() {
        // Jackson deserialization
    }

    public Saying(long id, String content) {
        this.id = id;
        this.content = content;
    }

    @JsonProperty
    public long getId() {
        return id;
    }

    @JsonProperty
    public String getContent() {
        return content;
    }
}
```

まずは、期待値をファイルに記述しておきます。

```fixtures/saying.json
{"id":1,"content":"Hello"}
```

次にテストクラスです。

```SayingTest.java
import static org.fest.assertions.api.Assertions.*;
import io.dropwizard.jackson.Jackson;
import io.dropwizard.testing.FixtureHelpers;

import org.junit.Test;

import com.fasterxml.jackson.databind.ObjectMapper;

public class SayingTest {

	private static final ObjectMapper MAPPER = Jackson.newObjectMapper();

	private static final String FIXTURE = "fixtures/saying.json";

	@Test
	public void deserializesFromJSON() throws Exception {
		final Saying target = new Saying(1, "Hello");
		final Saying expected = MAPPER.readValue(
				FixtureHelpers.fixture(FIXTURE), Saying.class);
		assertThat(target).isEqualsToByComparingFields(expected);
	}

	@Test
	public void serializesToJSON() throws Exception {
		final Saying target = new Saying(1, "Hello");
		final String actual = MAPPER.writeValueAsString(target);
		assertThat(actual).isEqualTo(FixtureHelpers.fixture(FIXTURE));
	}

}
```

テストケースは基本的にJacksonの```ObjectMapper```で「StringとRepresentationの変換」をします。
```FIXTURE```の内容を```FixtureHelpers.fixture()```で取得します。

アサーション部分は、hamcrest *ではありません。*
これは、 [*fest-assert*](https://github.com/alexruiz/fest-assert-2.x/wiki/Converting-JUnit-assertions-to-Fest-Assertions) という流れるようにアサーションが記述できるライブラリになります。
使わなくてもいいですが、```dropwizard-testing```が依存しているし、便利なので利用することをお勧めします。

## Resourceクラスのテスト

Resourceクラスは、主にURIリクエストからRepresentationを返します。

テスト対象のクラスはこちらです。

```PeopleResource.java
@Path("/people")
@Produces(MediaType.APPLICATION_JSON)
public class PeopleResource {

	private final PersonDao peopleDAO;

	public PeopleResource(PersonDao peopleDAO) {
		this.peopleDAO = peopleDAO;
	}

	@GET
	@UnitOfWork
	@Path("/{personId}")
	public Person getPerson(@PathParam("personId") LongParam personId) {
		final Optional<Person> person = peopleDAO.findById(personId.get());
		if (!person.isPresent()) {
			throw new NotFoundException("{status:notfound}");
		}
		return person.get();
	}
}
```

次にテストクラスです。
Mockitoを使っているのは、```dropwizard-testing```が依存しているからです。jmockitを使っても構いません。

```PeopleResourceTest.java
import static org.fest.assertions.api.Assertions.*;
import static org.mockito.Mockito.*;
import io.dropwizard.testing.junit.ResourceTestRule;

import org.junit.Before;
import org.junit.ClassRule;
import org.junit.Test;

import com.github.ko2ic.core.Person;
import com.github.ko2ic.db.PersonDao;
import com.google.common.base.Optional;

public class PeopleResourceTest {

	private static final PersonDao dao = mock(PersonDao.class);

	@ClassRule
	public static final ResourceTestRule resources = ResourceTestRule.builder()
			.addResource(new PeopleResource(dao)).build();

	private final Person person = new Person(1, "ko2ic", "job");

	@Before
	public void setup() {
		when(dao.findById(Long.parseLong("1"))).thenReturn(
				Optional.fromNullable(person));
	}

	@Test
	public void testGetPerson() {
		assertThat(resources.client().resource("/people/1").get(Person.class))
				.isEqualsToByComparingFields(person);
		verify(dao).findById(Long.parseLong("1"));
	}
}
```

ポイントは、```ResourceTestRule```です。
これは、Jerseyのリソースをテストするためのdropwizardのクラスになります。

# Integration Test


モックを使わないでフロントからDBまでのテストの自動化ができれば便利です。
Dropwizardではこれも簡単にできるようにクラスが用意されています。

```PeopleResourceIntegrationTest.java
public class PeopleResourceIntegrationTest {

	private static final String FIXTURE_PATH = "fixtures/integration/";

	@ClassRule
	public static final DropwizardAppRule<HelloWorldConfiguration> RULE = new DropwizardAppRule<>(
			HelloWorldApplication.class, "example.yml");

	@Test
	public void testGetPerson() throws JsonParseException,
			JsonMappingException, IOException {
		Client client = new Client();

		ClientResponse response = client.resource(
				String.format("http://localhost:%d/people/2",
						RULE.getLocalPort())).get(ClientResponse.class);

		assertThat(response.getStatus()).isEqualTo(200);

		String entity = response.getEntity(String.class);
		assertThat(entity).isEqualTo(
				FixtureHelpers.fixture(FIXTURE_PATH + "getPerson.json"));
	}
}
```

ポイントは```DropwizardAppRule```です。
あとは、ほとんどがjerseyのクラスになります。

このテストを動作させるとサーバーが起動して、対象のURLにアクセスした場合の戻り値を検証できます。

このサンプルでは、期待値をファイルに記述しています。

```fixtures/integration/getPerson.json
{"id":2,"fullName":"ko2ic second","jobTitle":"jobTitle2"}
```

# データ投入方法

このままテストを動作させても検証エラーになります。データを入れてないからです。
次はデータを入れる処理を追加します。liquibaseのAPIを利用します。
ポイントは、migrations.xmlとdata.xmlです。

```java
	private static Database database;

	private Liquibase migrations;

	@BeforeClass
	public static void beforeClass() throws SQLException, LiquibaseException {
		database = createDatabase();
	}

	@AfterClass
	public static void afterClass() throws DatabaseException, LockException {
		database.close();
		database = null;
	}

	@Before
	public void before() throws DatabaseException, SQLException,
			LiquibaseException {
		migrations = createLiquibase("migrations.xml");
		String context = null;
		migrations.update(context);
	}

	@After
	public void after() throws DatabaseException, LockException {
		migrations.dropAll();
	}

	private Liquibase createLiquibase(String migrations) throws SQLException,
			DatabaseException, LiquibaseException {
		Liquibase liquibase = new Liquibase(migrations,
				new ClassLoaderResourceAccessor(), database);
		return liquibase;
	}

	private static Database createDatabase() throws SQLException,
			DatabaseException {
		DataSourceFactory dataSourceFactory = RULE.getConfiguration()
				.getDataSourceFactory();
		Properties info = new Properties();
		info.setProperty("user", dataSourceFactory.getUser());
		info.setProperty("password", dataSourceFactory.getPassword());
		org.h2.jdbc.JdbcConnection h2Conn = new org.h2.jdbc.JdbcConnection(
				dataSourceFactory.getUrl(), info);
		JdbcConnection conn = new JdbcConnection(h2Conn);
		Database database = DatabaseFactory.getInstance()
				.findCorrectDatabaseImplementation(conn);
		return database;
	}

	@Test
	public void testGetPerson() throws JsonParseException,
			JsonMappingException, IOException, DatabaseException, SQLException,
			LiquibaseException {

		Liquibase data = createLiquibase("data.xml");
		data.update("testGetPerson");
		・・・
	}
```

migrations.xmlが本番でも利用するテーブルです。
 @Before、@Afterでテーブルを作成・削除しているのは、それぞれのテストを完全に独立させたいからです。

```src/main/resources/migrations.xml
<?xml version="1.0" encoding="UTF-8"?>

<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog
         http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.1.xsd">

	<changeSet id="1" author="ko2ic">
		<createTable tableName="people">
			<column name="id" type="bigint" autoIncrement="true">
				<constraints primaryKey="true" nullable="false" />
			</column>
			<column name="fullName" type="varchar(255)">
				<constraints nullable="false" />
			</column>
			<column name="jobTitle" type="varchar(255)" />
		</createTable>
	</changeSet>
	<changeSet id="2" author="ko2ic">
		<addColumn tableName="people">
			<column name="deleteFlag" type="boolean" defaultValue="0">
				<constraints nullable="false" />
			</column>
		</addColumn>
	</changeSet>	
</databaseChangeLog>

```

テスト用のデータを入れるためにdata.xmlを用意しています。
contextに```testGetPerson```という名前を付けて対象のテスト(testGetPerson)の始めに呼んでテストで必要なデータをinsertしています。

```src/test/resources/data.xml
<?xml version="1.0" encoding="UTF-8"?>

<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog
         http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.1.xsd">

   <changeSet id="1" author="ko2ic" context="testGetPerson">
        <insert tableName="people">
            <column name="id" value="1"/>
            <column name="fullName" value="ko2ic first"/>
            <column name="jobTitle" value="jobTitle1"/>
            <column name="deleteFlag" value="0"/>
        </insert>
        <insert tableName="people">
            <column name="id" value="2"/>
            <column name="fullName" value="ko2ic second"/>
            <column name="jobTitle" value="jobTitle2"/>
            <column name="deleteFlag" value="0"/>
        </insert>
        <insert tableName="people">
            <column name="id" value="3"/>
            <column name="fullName" value="ko2ic third"/>
            <column name="jobTitle" value="jobTitle3"/>
            <column name="deleteFlag" value="0"/>
        </insert>        
    </changeSet>	
</databaseChangeLog>
```

このサンプルでは、Integration用のテストデータを作るためにliquibaseのAPIを利用しましたが、DaoのUnit Testを行うときにもこの仕組みは利用できると思います。