testcontainersの使い方 〜Cassandra編〜

Cassandraに依存したアプリケーションのテストを、
実際にCassandraと接続してテストしたいケースがあるかと思います。

そんなときに、「testcontainers」を使うとテスト時に一時的に起動するCassandraを用いてテストすることができます!

この記事では、testcontainersを使ってCassandra周りの単体テスト・結合テストを実装する方法をまとめます。

実行環境

ビルドツールはGradleを使います。

build.gradleは以下の通りです。

plugins {
	id 'java'
	id 'org.springframework.boot' version '3.0.6'
	id 'io.spring.dependency-management' version '1.1.0'
}

group = 'hirabay'
version = '0.0.1'
sourceCompatibility = '17'

repositories {
	mavenCentral()
}

ext {
	set('testcontainersVersion', "1.18.0")
}

dependencies {
	implementation 'org.springframework.boot:spring-boot-starter-data-cassandra'
	implementation 'org.springframework.boot:spring-boot-starter-web'

	testImplementation 'org.springframework.boot:spring-boot-starter-test'
	testImplementation 'org.testcontainers:cassandra'
	testImplementation 'org.testcontainers:junit-jupiter'
}

dependencyManagement {
	imports {
		mavenBom "org.testcontainers:testcontainers-bom:${testcontainersVersion}"
	}
}

tasks.named('test') {
	useJUnitPlatform()
}

メモ
start.spring.io で以下の依存を選択すると同様のbuild.gradleが生成されます!
・Testcontainers
・Spring Data for Apache Cassandra

テスト対象のクラス

まずテスト対象のクラスを実装します。

@Component
@RequiredArgsConstructor
public class CassandraClient {
    private final CassandraTemplate cassandraTemplate;

    public List<Sample> findAll() {
        return cassandraTemplate.select("""
            SELECT *
            FROM sample.t_sample
            """, Sample.class);
    }
}
@Data
@Table("t_sample")
public class Sample {
    @PrimaryKey
    private String key;
    private String value;
}

CassandraTemplateを使ってCQLを実行するシンプルな作りです!

単体テスト

では先ほどのCassandraClientの単体テストを実装してみます。

@Slf4j
@Testcontainers
class CassandraClientTest {
    // テストコンテナを生成(裏でDockerコンテナが起動する)
    @Container
    public static CassandraContainer<?> cassandra = new CassandraContainer<>("cassandra:3.11.2")
            .withInitScript("initial.cql"); // cassandraの初期化

    private CassandraClient cassandraClient;

    @BeforeEach
    void setup() {
        // 起動したコンテナの情報を元にsessionを生成
        CqlSession cqlSession = CqlSession.builder()
                .addContactPoint(cassandra.getContactPoint())
                .withLocalDatacenter(cassandra.getLocalDatacenter())
                .build();

        var template = new CassandraTemplate(cqlSession);
        cassandraClient = new CassandraClient(template);
    }

    @Test
    void test() {
        var actual = cassandraClient.findAll();
        var expected = new Sample();
        expected.setKey("key1");
        expected.setValue("value1");

        assertThat(actual).isEqualTo(Collections.singletonList(expected));
    }
}
CREATE KEYSPACE sample
    WITH REPLICATION = {'class':'SimpleStrategy','replication_factor':1};

CREATE TABLE sample.t_sample (key text PRIMARY KEY, value text);

INSERT INTO sample.t_sample (key, value) VALUES ('key1', 'value1');

ポイント
・@Testcontainersが@BeforeAll, @AfterAllでコンテナの開始と終了を管理してくれる
・@Containerは@Testcontainersに管理対象のコンテナを知らせる
・CassandraContainer#withInitScriptを実行することでデータベースを初期化
・接続情報もCassandraContainerのインスタンスから取得

結合テスト

次に結合テストです。

本当はControllerやSserviceクラスを作ってEnd to Endでやりたいのですが、CassandraClientをAutowiredして使う方針で簡素化。。。

@Testcontainers
@SpringBootTest
class ApplicationTest {
	@Autowired
	private CassandraClient cassandraClient;


	// テストコンテナを生成(裏でDockerコンテナが起動する)
	@Container
	public static CassandraContainer<?> cassandra = new CassandraContainer<>("cassandra:3.11.2")
			.withInitScript("initial.cql"); // 初期化

	// propertiesを更新
	@DynamicPropertySource
	static void properties(DynamicPropertyRegistry registry) {
		registry.add("spring.cassandra.keyspace-name", () -> "sample");
		var contactPoint = "%s:%d".formatted(cassandra.getContactPoint().getHostName(), cassandra.getContactPoint().getPort());
		registry.add("spring.cassandra.contact-points", () -> contactPoint);
		registry.add("spring.cassandra.local-datacenter", () -> cassandra.getLocalDatacenter());
	}

	@Test
	void test() {
		var actual = cassandraClient.findAll();
		var expected = new Sample();
		expected.setKey("key1");
		expected.setValue("value1");

		assertThat(actual).isEqualTo(Collections.singletonList(expected));
	}
}

ポイント
・@DynamicPropertySourceを付与したメソッドで起動したコンテナの情報をもとにpropertyを更新する
・他は単体テストと同じ

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です