1. Ikhtisar
Dengan Spring Data JPA, Anda dapat dengan mudah membuat kueri basis data dan mengujinya menggunakan basis data H2 bawaan.
Tetapi kadang-kadang pengujian pada basis data nyata jauh lebih berguna , terutama jika kita menggunakan kueri yang terkait dengan implementasi basis data tertentu.
Dalam tutorial ini, kami akan menunjukkan cara menggunakan Testcontainers untuk pengujian integrasi dengan Spring Data JPA dan database PostgreSQL.
Pada artikel sebelumnya, kami membuat beberapa kueri basis data, terutama menggunakan penjelasan @Query , yang kami uji sekarang.
2. Konfigurasi
Untuk menggunakan database PostgreSQL dalam pengujian kami, kami harus menambahkan ketergantungan Testcontainers hanya tes dan driver PostgreSQL ke file pom.xml kami:
<dependency> <groupId>org.testcontainers</groupId> <artifactId>postgresql</artifactId> <version>1.10.6</version> <scope>test</scope> </dependency> <dependency> <groupId>org.postgresql</groupId> <artifactId>postgresql</artifactId> <version>42.2.5</version> </dependency>
Kami juga akan membuat file application.properties di direktori sumber daya pengujian, di mana kami akan mengatur penggunaan kelas driver yang diperlukan untuk Spring, serta membuat dan menghapus skema database setiap kali tes berjalan:
spring.datasource.driver-class-name=org.postgresql.Driver spring.jpa.hibernate.ddl-auto=create-drop
3. Tes unit
Untuk mulai menggunakan instance PostgreSQL dalam satu kelas uji, Anda perlu membuat definisi kontainer, dan kemudian menggunakan parameternya untuk membuat koneksi:
@RunWith(SpringRunner.class) @SpringBootTest @ContextConfiguration(initializers = {UserRepositoryTCIntegrationTest.Initializer.class}) public class UserRepositoryTCIntegrationTest extends UserRepositoryCommonIntegrationTests { @ClassRule public static PostgreSQLContainer postgreSQLContainer = new PostgreSQLContainer("postgres:11.1") .withDatabaseName("integration-tests-db") .withUsername("sa") .withPassword("sa"); static class Initializer implements ApplicationContextInitializer<ConfigurableApplicationContext> { public void initialize(ConfigurableApplicationContext configurableApplicationContext) { TestPropertyValues.of( "spring.datasource.url=" + postgreSQLContainer.getJdbcUrl(), "spring.datasource.username=" + postgreSQLContainer.getUsername(), "spring.datasource.password=" + postgreSQLContainer.getPassword() ).applyTo(configurableApplicationContext.getEnvironment()); } } }
Dalam contoh di atas, kami menggunakan @ClassRule
dari JUnit untuk mengonfigurasi wadah basis data sebelum menjalankan metode pengujian . Kami juga membuat kelas dalam statis yang mengimplementasikan ApplicationContextInitializer . Akhirnya, kami menerapkan penjelasan @ContextConfiguration
ke kelas pengujian kami dengan kelas inisialisasi sebagai parameter.
Setelah menyelesaikan tiga langkah ini, kita dapat mengatur parameter koneksi sebelum menerbitkan konteks Spring.
Sekarang kami menggunakan dua permintaan UPDATE dari artikel sebelumnya:
@Modifying @Query("update User u set u.status = :status where u.name = :name") int updateUserSetStatusForName(@Param("status") Integer status, @Param("name") String name); @Modifying @Query(value = "UPDATE Users u SET u.status = ? WHERE u.name = ?", nativeQuery = true) int updateUserSetStatusForNameNative(Integer status, String name);
Dan uji dalam lingkungan runtime yang disetel:
@Test @Transactional public void givenUsersInDB_WhenUpdateStatusForNameModifyingQueryAnnotationJPQL_ThenModifyMatchingUsers(){ insertUsers(); int updatedUsersSize = userRepository.updateUserSetStatusForName(0, "SAMPLE"); assertThat(updatedUsersSize).isEqualTo(2); } @Test @Transactional public void givenUsersInDB_WhenUpdateStatusForNameModifyingQueryAnnotationNative_ThenModifyMatchingUsers(){ insertUsers(); int updatedUsersSize = userRepository.updateUserSetStatusForNameNative(0, "SAMPLE"); assertThat(updatedUsersSize).isEqualTo(2); } private void insertUsers() { userRepository.save(new User("SAMPLE", "email@example.com", 1)); userRepository.save(new User("SAMPLE1", "email2@example.com", 1)); userRepository.save(new User("SAMPLE", "email3@example.com", 1)); userRepository.save(new User("SAMPLE3", "email4@example.com", 1)); userRepository.flush(); }
Dalam skenario di atas, tes pertama berhasil, dan yang kedua melempar InvalidDataAccessResourceUsageException dengan pesan:
Caused by: org.postgresql.util.PSQLException: ERROR: column "u" of relation "users" does not exist
Jika kami menjalankan tes yang sama menggunakan database H2 bawaan, keduanya akan berhasil, tetapi PostgreSQL tidak menerima alias dalam pernyataan SET. Kami dapat dengan cepat memperbaiki permintaan dengan menghapus alias yang bermasalah:
@Modifying @Query(value = "UPDATE Users u SET status = ? WHERE u.name = ?", nativeQuery = true) int updateUserSetStatusForNameNative(Integer status, String name);
Kali ini kedua tes berhasil lulus. Dalam contoh ini, kami menggunakan Testcontainers untuk mengidentifikasi masalah dengan kueri asli yang kalau tidak akan terdeteksi hanya setelah pergi ke database produksi . Juga harus dicatat bahwa menggunakan query JPQL umumnya lebih aman, karena Spring menerjemahkannya dengan benar tergantung pada penyedia database yang digunakan.
4. Contoh database bersama
Di bagian sebelumnya, kami menjelaskan cara menggunakan Testcontainers dalam satu tes. Dalam kasus nyata, saya ingin menggunakan wadah database yang sama dalam beberapa tes karena waktu peluncuran yang relatif lama.
Mari kita membuat kelas umum untuk membuat wadah database dengan mewarisi PostgreSQLContainer dan mengganti metode start () dan stop () :
public class BaeldungPostgresqlContainer extends PostgreSQLContainer<BaeldungPostgresqlContainer> { private static final String IMAGE_VERSION = "postgres:11.1"; private static BaeldungPostgresqlContainer container; private BaeldungPostgresqlContainer() { super(IMAGE_VERSION); } public static BaeldungPostgresqlContainer getInstance() { if (container == null) { container = new BaeldungPostgresqlContainer(); } return container; } @Override public void start() { super.start(); System.setProperty("DB_URL", container.getJdbcUrl()); System.setProperty("DB_USERNAME", container.getUsername()); System.setProperty("DB_PASSWORD", container.getPassword()); } @Override public void stop() {
Membiarkan metode stop () kosong, kami mengaktifkan JVM untuk menangani penyelesaian kontainer sendiri. Kami juga menerapkan singleton sederhana, di mana hanya tes pertama yang memulai wadah, dan setiap tes berikutnya menggunakan contoh yang ada. Pada metode start () , kami menggunakan System # setProperty untuk menyimpan parameter koneksi ke variabel lingkungan.
Sekarang kita dapat menulisnya ke file application.properties :
spring.datasource.url=${DB_URL} spring.datasource.username=${DB_USERNAME} spring.datasource.password=${DB_PASSWORD}
Sekarang kita menggunakan kelas utilitas kita dalam definisi tes:
@RunWith(SpringRunner.class) @SpringBootTest public class UserRepositoryTCAutoIntegrationTest { @ClassRule public static PostgreSQLContainer postgreSQLContainer = BaeldungPostgresqlContainer.getInstance();
Seperti dalam contoh sebelumnya, kami menerapkan penjelasan @ClassRule ke bidang dengan definisi wadah. Dengan demikian, parameter koneksi DataSource diisi dengan nilai yang benar sebelum membuat konteks Spring.
Sekarang kita dapat mengimplementasikan beberapa tes menggunakan instance database yang sama dengan hanya mengatur bidang dengan penjelasan @ClassRule yang dibuat menggunakan kelas utilitas kami BaeldungPostgresqlContainer .
5. Kesimpulan
Dalam artikel ini, kami menunjukkan metode pengujian pada basis data produksi menggunakan Testcontainers.
Kami juga memeriksa contoh menggunakan uji tunggal menggunakan mekanisme ApplicationContextInitializer dari Spring, serta menerapkan kelas untuk menggunakan kembali contoh database.
Kami juga menunjukkan bagaimana Testcontainers dapat membantu mengidentifikasi masalah kompatibilitas antara beberapa penyedia basis data, terutama untuk kueri asli.
Seperti biasa, kode lengkap yang digunakan dalam artikel ini tersedia di GitHub .