19 Ocak 2021 Salı

SpringTest Testcontainers Kullanımı

Giriş
Testcontainers  Spring initializr sayfasında var. Oradan eklemesi daha kolay. 
Maven
Örnek
Eğer elle eklemek istersek ana pom'u eklemek için yani BOM (Bill Of Materials) için şöyle yaparız
<dependencyManagement>
  <dependencies>
    <dependency>
      <groupId>org.testcontainers</groupId>
      <artifactId>testcontainers-bom</artifactId>
      <version>${testcontainers.version}</version>
      <type>pom</type>
      <scope>import</scope>
    </dependency>
  </dependencies>
</dependencyManagement>
Şu satırı dahil ederiz. junit-jupiter bağımlılığı eğer JUnit5 kullanıyorsak gerekiyor.
<dependency>
  <groupId>org.testcontainers</groupId>
  <artifactId>testcontainers</artifactId>
  <scope>test</scope>
</dependency>
<dependency>
  <groupId>org.testcontainers</groupId>
  <artifactId>junit-jupiter</artifactId>
  <scope>test</scope>
</dependency>
Daha sonra gerekli DB container'ı eklemek için şöyle yaparız. Burada JUnit5 desteği ve Neo4J ekleniyor
<dependency>
  <groupId>org.testcontainers</groupId>
  <artifactId>neo4j</artifactId>
  <scope>test</scope>
</dependency>
Örnek
Eğer testcontainers için parent pom'u eklemek istemezsek şöyle yaparız
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-mongodb</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>org.testcontainers</groupId>
    <artifactId>mongodb</artifactId>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>org.testcontainers</groupId>
    <artifactId>spock</artifactId>
    <scope>test</scope>
</dependency>
Kullanım
1. Test sınıfına @Testcontainers anotasyonu eklenir
2. Test sınıfında @Container anotasyonu ile container tanımlanır
3. Container tarafından kullanılan property'ler @DynamicPropertySource ile spring'e tanıtılır.
4. Container tarafından kullanılan property'ler @ContextConfiguration ile spring'e tanıtılır.
5. Container tarafından kullanılacak property'ler test dizini altındaki application.properties'ten atanır

Örnek - @DynamicPropertySource
Şöyle yaparız
@SpringBootTest
@Testcontainers
public class PostRepositoryWithTestContainersTest {
  @Container
  static Neo4jContainer<?> neo4jContainer = new Neo4jContainer<>("neo4j:4.0")
    .withStartupTimeout(Duration.ofMinutes(5));

  @DynamicPropertySource
  static void neo4jProperties(DynamicPropertyRegistry registry) {
    registry.add("spring.neo4j.uri", neo4jContainer::getBoltUrl);
    registry.add("spring.neo4j.authentication.username", () -> "neo4j");
    registry.add("spring.neo4j.authentication.password", neo4jContainer::getAdminPassword);
  }
  ...
}
Örnek - MySQLContainer 

Örnek - PostgreSQLContainer 

Örnek - MongoDBContainer

Örnek - RabitMQ
Bir örnek burada

Örnek - Couchbase
Şu satırı dahil ederiz
testImplementation("org.testcontainers:couchbase:1.16.3")
testImplementation("org.testcontainers:testcontainers:1.16.3")
testImplementation("org.testcontainers:junit-jupiter:1.16.3")

Örnek - @ContextConfiguration
Şöyle yaparız. Burada Spring için bir sürü ayar atanabiliyor.
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@ContextConfiguration(initializers = UserRepositoryTest.Initializer.class)
class UserRepositoryTest {

  @Autowired
  private UserRepository userRepository;
    
  @Test
  void findByUsername() {

    User john = ...;
    User savedUser = userRepository.save(john);

    assertEquals(1, savedUser.getId());
    assertEquals(john, userRepository.getOne(1L));
    assertEquals(john, userRepository.findByUsername("johnsmith"));

    User anna = ...;
    savedUser = userRepository.save(anna);

    assertEquals(2, savedUser.getId());
    assertEquals(anna, userRepository.getOne(2L));
    assertEquals(anna, userRepository.findByUsername("annasmith"));
  }
}
Ayarları atamak için şöyle yaparız. Burada Spring için bir sürü ayar atanabiliyor.
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@ContextConfiguration(initializers = UserRepositoryTest.Initializer.class)
class UserRepositoryTest {
  ...
  public static class Initializer implements
ApplicationContextInitializer<ConfigurableApplicationContext> {

    @Container
    private static MySQLContainer<?> mysql = new MySQLContainer<>("mysql:8");

    static {
      mysql.start();
    }

    @Override
    public void initialize(ConfigurableApplicationContext applicationContext) {

      TestPropertyValues.of(
        "spring.jpa.hibernate.ddl-auto=none",
        "spring.datasource.initialization-mode=always",
        "spring.datasource.platform=mysql",
        "spring.jpa.properties.hibernate.jdbc.lob.non_contextual_creation=true",
        "spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver",
        "spring.datasource.url=" + mysql.getJdbcUrl(),
        "spring.datasource.username=" + mysql.getUsername(),
        "spring.datasource.password=" + mysql.getPassword(),
        "spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQLDialect",
        "spring.jpa.properties.hibernate.enable_lazy_load_no_trans=true",
        "spring.liquibase.change-log=classpath:/db/changelog/db.changelog-master.xml",
        "spring.liquibase.contexts=test",
        "logging.level.liquibase=INFO"
      ).applyTo(applicationContext);
    }
  }
}
TestContainer Nesnesinin Tüm Testlerde Paylaşılması
Örnek
Şöyle bir ata test sınıfı tanımlanır
@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
@Testcontainers @Import(Configuration.class) public abstract class BaseApiTest { @Container static final PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>("postgres:10.9"); @Container static final MongoDBContainer mongo = new MongoDBContainer("mongo:3.6-xenial"); @DynamicPropertySource static void properties(DynamicPropertyRegistry registry) { registry.add("spring.datasource.url", postgres::getJdbcUrl); registry.add("spring.datasource.username", postgres::getUsername); registry.add("spring.datasource.password", postgres::getPassword); registry.add("spring.data.mongodb.uri", mongo::getReplicaSetUrl); } @TestConfiguration public static class Configuration { @Lazy @Bean("testSetupMongoClient") public MongoClient testSetupMongoClient() { return MongoClients.create(mongo.getReplicaSetUrl("test")); } @Lazy @Bean("testSetupDataSource") public DataSource testInitDataSource() { PGSimpleDataSource ds = new PGSimpleDataSource(); ds.setUrl(postgres.getJdbcUrl()); ds.setDatabaseName(postgres.getDatabaseName()); ds.setUser(postgres.getUsername()); ds.setPassword(postgres.getPassword()); return ds; } } }
İşleri kolaylaştırıcı şöyle anotasyonlar tanımlanır
@Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER, 
  ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Autowired
@Qualifier("testSetupDataSource")
public @interface TestSetupDataSource {

}

@Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER, 
  ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Autowired
@Qualifier("testSetupMongoClient")
public @interface TestSetupMongoClient {

}

@Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER, 
  ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Autowired
@Qualifier("testSetupMongoClient")
public @interface TestSetupMongoClient {

}
Gerçek testler için ortak sınıf tanımlanır
public class ApiTest extends BaseApiTest {

  @Nested
  class Companies extends CompaniesApiTest {}

  @Nested
  class Esg extends EsgApiTest {}

  @Nested
  class Controversies extends ControversiesApiTest {}

}
Testler şöyledir
@TestInstance(Lifecycle.PER_CLASS)
abstract public class EsgApiTest {

  @TestSetupMongoClient
  private MongoClient mongoClient;

  @TestSetupDataSource
  private DataSource dataSource;

  @ApiBasePath
  private String apiBasePath;

  @BeforeAll
  public void beforeAll() throws SQLException {
    try (var con = dataSource.getConnection()) {
      var st = con.createStatement();
      st.executeUpdate("drop table if exists companies");
      st.executeUpdate("create table companies(id text, name text)");
      st.executeUpdate("insert into companies(id, name) values ('AAPL', 'Apple')");
      st.executeUpdate("insert into companies(id, name) values ('GOOGL', 'Google')");
    }
    Document apple = new Document(Map.of("_id", "AAPL", "...", 86,));
    Document google = new Document(Map.of("_id", "GOOGL", "...", 76));
    var scores = mongoClient.getDatabase("test").getCollection("scores");
    scores.drop();
    scores.insertMany(List.of(apple, google));
  }

  @Test
  void companyEsgScore() {
    //Burada rest çağrısı yapılabilir. Çünkü DB doludur
  }
}





Hiç yorum yok:

Yorum Gönder