13 Kasım 2022 Pazar

Postman Script

Örnek
Elimizde şöyle bir ön script olsun
function randomString() {
  ...
}
pm.collectionVariables.set('randomEmailPrefix', randomString());
Bir istek gönderelim
{
  "notificationEmail": "{{randomEmailPrefix}}@gmail.com",
  "customerType": "INDIVIDUAL"
}
Test metodlarını şöyle yaparız
pm.test("Status code is 200", function () {
    pm.response.to.have.status(200);
});

pm.test("Customer has created successfully", function () {
    var jsonData = pm.response.json();
    pm.expect(jsonData).is.not.null;
    pm.expect(jsonData.id).is.not.null;
});

pm.test("Response has a valid json body", function () {
     pm.response.to.be.ok;
     pm.response.to.be.withBody;
     pm.response.to.be.json;
});

pm.collectionVariables.set("createdCustomerId",  pm.response.json().id);





11 Kasım 2022 Cuma

SpringBoot Test @AutoConfigureMockMvc Anotasyonu

Giriş
Şöyle yaparız
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
Örnek
Şöyle yaparız
import static org.hamcrest.Matchers.containsString;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.web.servlet.MockMvc;

@SpringBootTest
@AutoConfigureMockMvc
public class HelloControllerMockMvcTest {

  @Autowired
  private MockMvc mockMvc;

  @Test
  public void shouldReturnDefaultMessage() throws Exception {
    this.mockMvc.perform(get("/"))
      .andDo(print())
      .andExpect(status().isOk())
      .andExpect(content().string(containsString("Hello World!")));
  }
}


SpringBoot Test @RestController Nasıl Test Edilir

Giriş
Yöntemler şöyle
1. @SpringBootTest ile uygulama ayağa kaldırılır ve MockMvc ile RestController test edilir
2. @WebMvcTest ile Sliced Test yapılır ve MockMvc ile test edilir
3. @SpringBootTest ile uygulama ayağa kaldırılır ve TestRestTemplate ile RestController test edilir
4. Sadece Mockito ile RestController test edilir. @Mock + @InjectMocks kullanılır

9 Kasım 2022 Çarşamba

SpringTest Testcontainers Kullanımı - TestContainer Sınıfından Kalıtma

Giriş
- Bu kullanım Spring'in @DynamicPropertySource anotasyonunu kullanmadığı için daha çok Spring kullanmayan ama Jupiter ile birim testi yapmak isteyen projeler için daha uygun

- TestContainer başlatıldıktan sonra url, username gibi şeyler System property olarak atanıyor. Böylece spring bunlara erişebiliyor.

Örnek
Şöyle yaparız
import org.testcontainers.containers.OracleContainer;

public class OracleTestContainer extends OracleContainer {

  public static final String DB_CONNECTION_URL = "DB_URL";
  public static final String DB_USERNAME = "DB_USERNAME";
  public static final String DB_PASSWORD = "DB_PASSWORD";
  private static final String IMAGE_VERSION = "oracleinanutshell/oracle-xe-11g:1.0.0";
  private static OracleTestContainer container;

  private OracleTestContainer() {
    super(IMAGE_VERSION);
  }

  public static OracleTestContainer getInstance(String initScriptPath) {
    if (container == null) {
      container = new OracleTestContainer();
      container.withInitScript(initScriptPath);
      container.start();
    }
    return container;
  }

  @Override
  public void start() {
    super.start();
    System.setProperty(DB_CONNECTION_URL, container.getJdbcUrl());
    System.setProperty(DB_USERNAME, container.getUsername());
    System.setProperty(DB_PASSWORD, container.getPassword());
  }
}
Testlerde kullanılacak bir SpringConfig sınıfı tanımlanır. Şöyle yaparız
@ComponentScan(basePackages = {"com.test.app"})
@EnableJpaRepositories(basePackages = "com.test.app")
@EntityScan(basePackageClasses = {RocApplicant.class, ... })
public class ApplicantTestConfig {

}
Soyut bir test sınıfı yaratılır. Şöyle yaparız
@ActiveProfiles("test")
@DataJpaTest
@ContextConfiguration(classes = {ApplicantTestConfig.class})
@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
@Rollback(false)
@EnableAutoConfiguration
public abstract class AbstractTest {

  public static final String INIT_SCRIPT = "sql/test-container-init.sql";

  public static OracleContainer oracle = OracleTestContainer.getInstance(INIT_SCRIPT);

  @BeforeAll
  static void setUp() {
    if (!oracle.isCreated()) {
      oracle.start();
    }
  }
}
Spring'in bağlanabilmesi için application-test.yaml dosyası oluşturulur. Şöyle yaparız
spring:
  datasource:
    driverClassName: oracle.jdbc.OracleDriver
    url: ${DB_URL}
    username: ${DB_USERNAME}
    password: ${DB_PASSWORD}
    hikari:
      maximum-pool-size: 2
  test:
    database:
      replace: none
  main:
    allow-bean-definition-overriding: true
  jpa:
    hibernate:
      ddl-auto: none
      database-platform: org.hibernate.dialect.Oracle10gDialect
test sınıfı şöyledir. Burada ilk sql veri tabanını temizler, ikincisi ise doldurur.
class RocApplicantServiceTest extends AbstractTest {
    
  @Autowired
  RocApplicantService mnsRocApplicantService;

  @Test
  @Sql(value = {"classpath:/sql/clean_roc_applicants.sql",
                "classpath:/sql/insert_roc_applicants.sql"})
  void createApplicant() {
    var response = mnsRocApplicantService.createApplicant(...);
    assertNotNull(response);
  }
}


3 Kasım 2022 Perşembe

SpringTest Testcontainers Kafka + Zookeeper + Avro Schema Registry

Giriş
Şu satırı dahil ederiz
import com.github.dockerjava.api.model.HostConfig;
import com.github.dockerjava.api.model.PortBinding;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
import org.springframework.test.context.DynamicPropertyRegistry;
import org.springframework.test.context.DynamicPropertySource;
import org.testcontainers.containers.GenericContainer;
import org.testcontainers.containers.Network;
import org.testcontainers.containers.output.Slf4jLogConsumer;
import org.testcontainers.junit.jupiter.Container;
import org.testcontainers.junit.jupiter.Testcontainers;
import org.testcontainers.utility.DockerImageName;
Şöyle yaparız
@SpringBootTest(properties = "spring.main.web-application-type=reactive")
@Testcontainers
public class AbstractSpringTest {
  public static final int KAFKA_PORT = 19092;
  private static final Logger LOGGER = LoggerFactory.getLogger(AbstractSpringTest.class);
  private static final Network SHARED_NETWORK = Network.newNetwork();
  private static final int SCHEMA_REGISTRY_PORT = 8081;
  @Container
  static GenericContainer<?> zookeeper = zkContainer();
  @Container
  static GenericContainer<?> kafka = kafkaContainer();
  @Container
  static GenericContainer<?> schemaRegistry = schemaRegistryContainer();


  @DynamicPropertySource
  static void registerPgProperties(DynamicPropertyRegistry registry) {
    registry.add("kafka.bootstrap-servers", () -> kafka.getHost()+":"+kafka.getMappedPort(KAFKA_PORT));
    registry.add("kafka.schema.registry.url", () -> String.format("http://%s:%d",
                schemaRegistry.getHost(), schemaRegistry.getMappedPort(SCHEMA_REGISTRY_PORT)));
    registry.add("kafka.consumer.enabled",()->"true");
  }

  private static GenericContainer<?> zkContainer() {
    return new GenericContainer<>(DockerImageName.parse("confluentinc/cp-zookeeper:5.4.10-1-ubi8"))
      .withExposedPorts(2181)
      .withCreateContainerCmdModifier(cmd ->
        cmd.withName("zookeeper")
          .withHostConfig(HostConfig.newHostConfig()
                         .withNetworkMode(SHARED_NETWORK.getId())
                         .withPortBindings(PortBinding.parse("12181:2181"))
          )
        )
        .withLogConsumer(new Slf4jLogConsumer(LOGGER))
        .withNetwork(SHARED_NETWORK).withNetworkMode(SHARED_NETWORK.getId())
        .withNetworkAliases("zookeeper")
        .withEnv("ZOOKEEPER_SERVER_ID", "1")
        .withEnv("ZOOKEEPER_CLIENT_PORT", "2181")
        .withEnv("ZOOKEEPER_TICK_TIME", "2000")
        .withEnv("ZOOKEEPER_SERVERS", "zookeeper:22888:23888");
    }

  private static GenericContainer<?> kafkaContainer() {
    return new GenericContainer<>(DockerImageName.parse("confluentinc/cp-kafka:7.2.2"))
      .withCreateContainerCmdModifier(cmd ->
        cmd.withName("kafka")
           .withHostConfig(HostConfig.newHostConfig()
                                     .withNetworkMode(SHARED_NETWORK.getId())
                                     .withPortBindings(PortBinding.parse(KAFKA_PORT+":"+KAFKA_PORT))
                              )
        )
        .withNetwork(SHARED_NETWORK).withNetworkMode(SHARED_NETWORK.getId())
        .withNetworkAliases("kafka")
        .withAccessToHost(true)
        .withExposedPorts(KAFKA_PORT)
        .withEnv("KAFKA_SCHEMA_REGISTRY_URL", "schemaregistry:8081")
        .withEnv("KAFKA_ZOOKEEPER_CONNECT", "zookeeper:2181")
        .withEnv("KAFKA_LISTENER_SECURITY_PROTOCOL_MAP", "PLAINTEXT:PLAINTEXT,PLAINTEXT_HOST:PLAINTEXT")
        .withEnv("KAFKA_INTER_BROKER_LISTENER_NAME", "PLAINTEXT")
        .withEnv("KAFKA_ADVERTISED_LISTENERS", "PLAINTEXT://kafka:9092,PLAINTEXT_HOST://localhost:19092")
        .withEnv("KAFKA_BROKER_ID", "1")
        .withEnv("KAFKA_BROKER_RACK", "r1")
        .withEnv("KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR", "1")
        .withEnv("KAFKA_DELETE_TOPIC_ENABLE", "true")
        .withEnv("KAFKA_AUTO_CREATE_TOPICS_ENABLE", "true")
        .withEnv("KAFKA_JMX_PORT", "9991")
        .withLogConsumer(new Slf4jLogConsumer(LOGGER))
        .dependsOn(zookeeper).withStartupTimeout(Duration.ofMinutes(2L));
  }

  private static GenericContainer<?> schemaRegistryContainer() {
    return new GenericContainer<>(DockerImageName.parse("confluentinc/cp-schema-registry:5.4.10"))
      .dependsOn(kafka)
      .withCreateContainerCmdModifier(cmd -> cmd.withName("schemaregistry")
                        .withHostConfig(HostConfig.newHostConfig()
                                .withNetworkMode(SHARED_NETWORK.getId())
                                .withPortBindings(PortBinding.parse("18081:8081"))
                        )
      )
      .withExposedPorts(SCHEMA_REGISTRY_PORT)
      .withNetwork(SHARED_NETWORK).withNetworkMode(SHARED_NETWORK.getId())
      .withNetworkAliases("schemaregistry")
      .withAccessToHost(true)
      .withEnv("SCHEMA_REGISTRY_KAFKASTORE_CONNECTION_URL", "zookeeper:2181")
      .withEnv("SCHEMA_REGISTRY_HOST_NAME", "schemaregistry")
      .withEnv("SCHEMA_REGISTRY_LISTENERS", "http://0.0.0.0:8081")
      .withLogConsumer(new Slf4jLogConsumer(LOGGER));
  }

  protected Resource readFile(String fileName) {
    return new ClassPathResource("./requests/" + fileName);
  }
}
Şö

SpringKafka Producer KafkaAdmin Sınıfı

Giriş
Şu satırı dahil ederiz
import org.springframework.kafka.core.KafkaAdmin;
Örnek
Şöyle yaparız
@Configuration
public class KafkaProducerConfig {
  ...
  @Value("${kafka.bootstrap-servers}")
  private String bootstrapServers;


  @Bean
  public KafkaAdmin admin() {
    
    LOGGER.info("{} = {}", AdminClientConfig.BOOTSTRAP_SERVERS_CONFIG, bootstrapServers);
    Map<String, Object> configs = new HashMap<>();
    configs.put(AdminClientConfig.BOOTSTRAP_SERVERS_CONFIG, bootstrapServers);
    
    KafkaAdmin kafkaAdmin = new KafkaAdmin(configs);
    kafkaAdmin.setFatalIfBrokerNotAvailable(true);
    kafkaAdmin.setBootstrapServersSupplier(() -> bootstrapServers);
    return kafkaAdmin;
  }
}

2 Kasım 2022 Çarşamba

SpringContext @Scope Anotasyonu - Singleton Scope

Giriş
Bir Spring bean için varsayılan değer budur. Bu bean'den tek bir tane yaratılır. 

Örnek
@Bean aslında @Scope("singleton") anlamına da gelir. Şöyle yaparız
@Bean
// @Scope("singleton")
// @Scope(value = ConfigurableBeanFactory.SCOPE_SINGLETON)
public Foo foo() {
  return new Foo();
}
How to guarantee thread-safety when Spring bean is default singleton
Çözümler şöyle
1 Use prototype
For the web application, we could consider add annotation @Scope(“prototype”) on controller or @Scope(“request”). For non-web application, we could add @Scope(“prototype”) on the class annotated with @Component

2. Use ThreadLocal

3. Use local variable

4. ConcurrentHashMap, ConcurrentHashSet

5. Use third-party middle-ware to store shared data