diff --git a/README.md b/README.md index 9cd2176..7baa897 100644 --- a/README.md +++ b/README.md @@ -247,9 +247,9 @@ In this example, the test is written in a readable DSL style, ensuring clarity a ./gradlew build ``` -3. Run the application: +3. Run the application in development mode: ```bash - ./gradlew bootRun + ./gradlew runDev ``` 4. The application will be accessible at `http://localhost:8080`. diff --git a/build.gradle.kts b/build.gradle.kts index af1cee2..dbf6c99 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -58,7 +58,7 @@ dependencies { implementation("io.github.openfeign:feign-jackson:12.4") implementation("org.springframework.cloud:spring-cloud-starter-openfeign:3.1.2") implementation("org.postgresql:postgresql:42.3.1") - implementation("org.testcontainers:postgresql:1.20.2") + implementation("org.flywaydb:flyway-core") // Spring Boot Test testImplementation("org.springframework.boot:spring-boot-starter-test") @@ -74,7 +74,10 @@ dependencies { testImplementation("org.springframework.boot:spring-boot-starter-test") testImplementation("org.wiremock:wiremock:3.9.1") testImplementation("org.testcontainers:junit-jupiter:1.20.2") - testImplementation("org.flywaydb:flyway-core") + testImplementation("org.testcontainers:testcontainers:1.20.2") + testImplementation("org.springframework.boot:spring-boot-testcontainers") + testImplementation("org.testcontainers:postgresql:1.20.2") + testRuntimeOnly("org.postgresql:postgresql") } @@ -125,3 +128,13 @@ tasks.wrapper { gradleVersion = "8.10.2" distributionType = Wrapper.DistributionType.ALL } + +tasks.register("runDev") { + group = "application" + description = "Run the application with DevelopmentTimeConfig and 'test' profile" + mainClass.set("camilyed.github.io.currencyexchangeapi.TestCurrencyExchangeApiApplicationKt") + classpath = sourceSets["main"].runtimeClasspath + sourceSets["integrationTest"].runtimeClasspath + args = listOf() + jvmArgs = listOf() + environment("SPRING_PROFILES_ACTIVE", "test") +} diff --git a/src/integrationTest/kotlin/camilyed/github/io/currencyexchangeapi/TestCurrencyExchangeApiApplication.kt b/src/integrationTest/kotlin/camilyed/github/io/currencyexchangeapi/TestCurrencyExchangeApiApplication.kt new file mode 100644 index 0000000..52470af --- /dev/null +++ b/src/integrationTest/kotlin/camilyed/github/io/currencyexchangeapi/TestCurrencyExchangeApiApplication.kt @@ -0,0 +1,13 @@ +package camilyed.github.io.currencyexchangeapi + +import camilyed.github.io.CurrencyExchangeApiApplication +import camilyed.github.io.currencyexchangeapi.testing.config.DevelopmentTimeConfig +import org.springframework.boot.builder.SpringApplicationBuilder + +class TestCurrencyExchangeApiApplication +fun main(args: Array) { + SpringApplicationBuilder() + .sources(CurrencyExchangeApiApplication::class.java, DevelopmentTimeConfig::class.java) + .profiles("test") + .run(*args) +} diff --git a/src/integrationTest/kotlin/camilyed/github/io/currencyexchangeapi/testing/BaseIntegrationTest.kt b/src/integrationTest/kotlin/camilyed/github/io/currencyexchangeapi/testing/BaseIntegrationTest.kt index 6fa5222..87a09b9 100644 --- a/src/integrationTest/kotlin/camilyed/github/io/currencyexchangeapi/testing/BaseIntegrationTest.kt +++ b/src/integrationTest/kotlin/camilyed/github/io/currencyexchangeapi/testing/BaseIntegrationTest.kt @@ -2,7 +2,7 @@ package camilyed.github.io.currencyexchangeapi.testing import camilyed.github.io.CurrencyExchangeApiApplication import camilyed.github.io.currencyexchangeapi.testing.abilties.MakeRequestAbility -import camilyed.github.io.currencyexchangeapi.testing.postgres.PostgresInitializer +import camilyed.github.io.currencyexchangeapi.testing.config.POSTGRES_SQL_CONTAINER import camilyed.github.io.currencyexchangeapi.testing.utils.DatabaseCleaner import com.fasterxml.jackson.databind.ObjectMapper import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper @@ -13,13 +13,13 @@ import org.junit.jupiter.api.BeforeEach import org.springframework.beans.factory.annotation.Autowired import org.springframework.boot.test.context.SpringBootTest import org.springframework.boot.test.web.client.TestRestTemplate +import org.springframework.boot.testcontainers.service.connection.ServiceConnection import org.springframework.test.context.ActiveProfiles import org.springframework.test.context.ContextConfiguration import org.springframework.test.context.DynamicPropertyRegistry import org.springframework.test.context.DynamicPropertySource @ContextConfiguration( - initializers = [PostgresInitializer::class], classes = [CurrencyExchangeApiApplication::class], ) @ActiveProfiles("test") @@ -43,7 +43,11 @@ class BaseIntegrationTest : MakeRequestAbility { } companion object { - protected val wireMock = WireMockServer(0) + + @ServiceConnection + val postgresSQLContainer = POSTGRES_SQL_CONTAINER + + private val wireMock = WireMockServer(0) @JvmStatic @DynamicPropertySource @@ -53,24 +57,55 @@ class BaseIntegrationTest : MakeRequestAbility { @JvmStatic @BeforeAll - fun startWireMock() { + fun start() { + startWiremock() + startPostgresContainer() + } + + private fun startWiremock() { if (!wireMock.isRunning) { + println("Starting Wiremock...") wireMock.start() WireMock.configureFor(wireMock.port()) - println("WIREMOCK STARTED at port: ${wireMock.port()}") + println("Wiremock started at port: ${wireMock.port()}") + } + } + + private fun startPostgresContainer() { + if (!postgresSQLContainer.isRunning) { + println("Starting PostgresContainer...") + postgresSQLContainer.start() + println("PostgresContainer started") } } init { + addShutdownHooks() + } + + private fun addShutdownHooks() { Runtime.getRuntime().addShutdownHook( Thread { - if (wireMock.isRunning) { - println("Shutting down WireMock server...") - wireMock.stop() - println("WireMock stopped") - } + shutdownWiremock() + shutdownPostgresContainer() }, ) } + + private fun shutdownWiremock() { + if (wireMock.isRunning) { + println("Shutting down WireMock server...") + wireMock.stop() + println("WireMock stopped") + } + } + + private fun shutdownPostgresContainer() { + if (postgresSQLContainer.isRunning) { + println("Shutting down PostgresContainer...") + postgresSQLContainer.stop() + println("PostgresContainer stopped") + } + } } } diff --git a/src/integrationTest/kotlin/camilyed/github/io/currencyexchangeapi/testing/config/DevelopmentTimeConfig.kt b/src/integrationTest/kotlin/camilyed/github/io/currencyexchangeapi/testing/config/DevelopmentTimeConfig.kt new file mode 100644 index 0000000..3b39b10 --- /dev/null +++ b/src/integrationTest/kotlin/camilyed/github/io/currencyexchangeapi/testing/config/DevelopmentTimeConfig.kt @@ -0,0 +1,13 @@ +package camilyed.github.io.currencyexchangeapi.testing.config + +import org.springframework.boot.test.context.TestConfiguration +import org.springframework.boot.testcontainers.service.connection.ServiceConnection +import org.springframework.context.annotation.Bean + +@TestConfiguration(proxyBeanMethods = false) +class DevelopmentTimeConfig { + + @Bean + @ServiceConnection + fun postgresSQLContainer() = POSTGRES_SQL_CONTAINER +} diff --git a/src/integrationTest/kotlin/camilyed/github/io/currencyexchangeapi/testing/config/PostgresSqlContainer.kt b/src/integrationTest/kotlin/camilyed/github/io/currencyexchangeapi/testing/config/PostgresSqlContainer.kt new file mode 100644 index 0000000..f220fc0 --- /dev/null +++ b/src/integrationTest/kotlin/camilyed/github/io/currencyexchangeapi/testing/config/PostgresSqlContainer.kt @@ -0,0 +1,11 @@ +package camilyed.github.io.currencyexchangeapi.testing.config + +import org.testcontainers.containers.PostgreSQLContainer + +val POSTGRES_SQL_CONTAINER = PostgreSQLContainer("postgres:13.4-alpine") + .apply { + withDatabaseName("test_db") + withUsername("test_user") + withPassword("test_password") + withInitScript("init.sql") + } diff --git a/src/integrationTest/kotlin/camilyed/github/io/currencyexchangeapi/testing/postgres/PostgresInitializer.kt b/src/integrationTest/kotlin/camilyed/github/io/currencyexchangeapi/testing/postgres/PostgresInitializer.kt deleted file mode 100644 index c9fa4a9..0000000 --- a/src/integrationTest/kotlin/camilyed/github/io/currencyexchangeapi/testing/postgres/PostgresInitializer.kt +++ /dev/null @@ -1,39 +0,0 @@ -package camilyed.github.io.currencyexchangeapi.testing.postgres - -import mu.KotlinLogging -import org.springframework.context.ApplicationContextInitializer -import org.springframework.context.ConfigurableApplicationContext -import org.springframework.context.annotation.Profile -import org.testcontainers.containers.PostgreSQLContainer - -@Profile("!test") -class PostgresInitializer : ApplicationContextInitializer { - override fun initialize(applicationContext: ConfigurableApplicationContext) { - log.info("Overriding system properties") - overrideSystemProperties() - } - - private fun overrideSystemProperties() { - System.setProperty("spring.datasource.url", pg.jdbcUrl) - System.setProperty("spring.datasource.username", pg.username) - System.setProperty("spring.datasource.password", pg.password) - } - - private companion object { - private val log = KotlinLogging.logger {} - private val pg: PostgreSQLContainer<*> = - PostgreSQLContainer("postgres:13.4-alpine") - .apply { - withReuse(true) - withDatabaseName("test_db") - withUsername("test_user") - withPassword("test_password") - } - - init { - pg.withInitScript("init.sql") - pg.start() - log.info("Postgres started") - } - } -} diff --git a/src/integrationTest/resources/application-test.yml b/src/integrationTest/resources/application-test.yml index f0b4cbd..e69de29 100644 --- a/src/integrationTest/resources/application-test.yml +++ b/src/integrationTest/resources/application-test.yml @@ -1,6 +0,0 @@ - -spring: - datasource: - url: jdbc:postgresql://localhost:5432/currency_exchange_db - username: myuser - password: mypassword diff --git a/src/main/kotlin/camilyed/github/io/CurrencyExchangeApiApplication.kt b/src/main/kotlin/camilyed/github/io/CurrencyExchangeApiApplication.kt index ece10c9..743770e 100644 --- a/src/main/kotlin/camilyed/github/io/CurrencyExchangeApiApplication.kt +++ b/src/main/kotlin/camilyed/github/io/CurrencyExchangeApiApplication.kt @@ -1,9 +1,8 @@ package camilyed.github.io -import camilyed.github.io.currencyexchangeapi.infrastructure.PostgresInitializer -import org.springframework.boot.SpringApplication import org.springframework.boot.autoconfigure.ImportAutoConfiguration import org.springframework.boot.autoconfigure.SpringBootApplication +import org.springframework.boot.runApplication import org.springframework.cloud.openfeign.EnableFeignClients import org.springframework.cloud.openfeign.FeignAutoConfiguration @@ -13,7 +12,5 @@ import org.springframework.cloud.openfeign.FeignAutoConfiguration class CurrencyExchangeApiApplication fun main(args: Array) { - val application = SpringApplication(CurrencyExchangeApiApplication::class.java) - application.addInitializers(PostgresInitializer()) - application.run(*args) + runApplication(*args) } diff --git a/src/main/kotlin/camilyed/github/io/currencyexchangeapi/config/DataSourceConfig.kt b/src/main/kotlin/camilyed/github/io/currencyexchangeapi/config/DataSourceConfig.kt deleted file mode 100644 index 6888877..0000000 --- a/src/main/kotlin/camilyed/github/io/currencyexchangeapi/config/DataSourceConfig.kt +++ /dev/null @@ -1,25 +0,0 @@ -package camilyed.github.io.currencyexchangeapi.config - -import org.springframework.beans.factory.annotation.Value -import org.springframework.context.annotation.Bean -import org.springframework.context.annotation.Configuration -import org.springframework.jdbc.datasource.DriverManagerDataSource -import javax.sql.DataSource - -@Configuration -class DataSourceConfig { - - @Bean - fun testDataSource( - @Value("\${spring.datasource.url}") url: String, - @Value("\${spring.datasource.username}") username: String, - @Value("\${spring.datasource.password}") password: String, - ): DataSource { - return DriverManagerDataSource().apply { - setDriverClassName("org.postgresql.Driver") - setUrl(url) - setUsername(username) - setPassword(password) - } - } -} diff --git a/src/main/kotlin/camilyed/github/io/currencyexchangeapi/infrastructure/PostgresInitializer.kt b/src/main/kotlin/camilyed/github/io/currencyexchangeapi/infrastructure/PostgresInitializer.kt deleted file mode 100644 index 29a2e5a..0000000 --- a/src/main/kotlin/camilyed/github/io/currencyexchangeapi/infrastructure/PostgresInitializer.kt +++ /dev/null @@ -1,47 +0,0 @@ -package camilyed.github.io.currencyexchangeapi.infrastructure - -import mu.KotlinLogging -import org.springframework.beans.factory.DisposableBean -import org.springframework.context.ApplicationContextInitializer -import org.springframework.context.ConfigurableApplicationContext -import org.springframework.context.annotation.Profile -import org.testcontainers.containers.PostgreSQLContainer - -@Profile("!test") -class PostgresInitializer : ApplicationContextInitializer, DisposableBean { - override fun initialize(applicationContext: ConfigurableApplicationContext) { - log.info("Overriding system properties") - overrideSystemProperties() - } - - override fun destroy() { - if (pg.isRunning) { - log.info("Stopping PostgreSQL container...") - pg.stop() - } - } - - private fun overrideSystemProperties() { - System.setProperty("spring.datasource.url", pg.jdbcUrl) - System.setProperty("spring.datasource.username", pg.username) - System.setProperty("spring.datasource.password", pg.password) - } - - private companion object { - private val log = KotlinLogging.logger {} - private val pg: PostgreSQLContainer<*> = - PostgreSQLContainer("postgres:13.4-alpine") - .apply { - withReuse(true) - withDatabaseName("test_db") - withUsername("test_user") - withPassword("test_password") - } - - init { - pg.withInitScript("init.sql") - pg.start() - log.info("Postgres started") - } - } -}