Dropwizardでテストする際、dropwizard-testing
を使うといくらか便利だったので記述しておきます。
maven設定
<dependency>
<groupId>io.dropwizard</groupId>
<artifactId>dropwizard-testing</artifactId>
<version>${dropwizard.version}</version>
<scope>test</scope>
</dependency>
Unit Test
Representationクラスのテスト
RepresentationはJacksonで変換対象になるクラスになります。
テスト対象のクラスはこちらです。
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;
}
}
まずは、期待値をファイルに記述しておきます。
{"id":1,"content":"Hello"}
次にテストクラスです。
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 という流れるようにアサーションが記述できるライブラリになります。
使わなくてもいいですが、dropwizard-testing
が依存しているし、便利なので利用することをお勧めします。
Resourceクラスのテスト
Resourceクラスは、主にURIリクエストからRepresentationを返します。
テスト対象のクラスはこちらです。
@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を使っても構いません。
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ではこれも簡単にできるようにクラスが用意されています。
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にアクセスした場合の戻り値を検証できます。
このサンプルでは、期待値をファイルに記述しています。
{"id":2,"fullName":"ko2ic second","jobTitle":"jobTitle2"}
データ投入方法
このままテストを動作させても検証エラーになります。データを入れてないからです。
次はデータを入れる処理を追加します。liquibaseのAPIを利用します。
ポイントは、migrations.xmlとdata.xmlです。
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でテーブルを作成・削除しているのは、それぞれのテストを完全に独立させたいからです。
<?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しています。
<?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を行うときにもこの仕組みは利用できると思います。