diff --git a/testing-modules/pom.xml b/testing-modules/pom.xml index 3dbb73973200..2d1d35680efb 100644 --- a/testing-modules/pom.xml +++ b/testing-modules/pom.xml @@ -59,6 +59,7 @@ spring-mockito spring-testing-2 spring-testing-3 + spring-testing-4 spring-testing testing-assertions test-containers diff --git a/testing-modules/spring-testing-4/pom.xml b/testing-modules/spring-testing-4/pom.xml new file mode 100644 index 000000000000..aa248fbaae16 --- /dev/null +++ b/testing-modules/spring-testing-4/pom.xml @@ -0,0 +1,93 @@ + + + 4.0.0 + spring-testing-4 + 0.1-SNAPSHOT + spring-testing-4 + + + com.baeldung + parent-boot-3 + 0.0.1-SNAPSHOT + ../../parent-boot-3 + + + + + org.springframework.boot + spring-boot-starter-web + + + com.zaxxer + HikariCP + + + org.postgresql + postgresql + + + + + com.github.seregamorph + spring-test-smart-context + 0.14 + test + + + org.springframework.boot + spring-boot-starter-test + test + + + org.springframework + spring-jdbc + test + + + org.testcontainers + postgresql + test + + + + + 17 + true + + + + + + org.apache.maven.plugins + maven-surefire-plugin + ${maven-surefire-plugin.version} + + + + *IntTest.java + + + + + org.apache.maven.plugins + maven-failsafe-plugin + ${maven-surefire-plugin.version} + + + + integration-test + verify + + + + + + *IntTest.java + + + + + + diff --git a/testing-modules/spring-testing-4/src/main/java/com/java/baeldung/spring/test/ArticlesController.java b/testing-modules/spring-testing-4/src/main/java/com/java/baeldung/spring/test/ArticlesController.java new file mode 100644 index 000000000000..55997b09ab0e --- /dev/null +++ b/testing-modules/spring-testing-4/src/main/java/com/java/baeldung/spring/test/ArticlesController.java @@ -0,0 +1,16 @@ +package com.java.baeldung.spring.test; + +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequestMapping("/articles") +public class ArticlesController { + + @GetMapping("/{id}") + public String get(@PathVariable("id") long id) { + return "Content " + id; + } +} diff --git a/testing-modules/spring-testing-4/src/test/java/com/java/baeldung/spring/test/AbstractIntegrationTest.java b/testing-modules/spring-testing-4/src/test/java/com/java/baeldung/spring/test/AbstractIntegrationTest.java new file mode 100644 index 000000000000..7f00f576ce6d --- /dev/null +++ b/testing-modules/spring-testing-4/src/test/java/com/java/baeldung/spring/test/AbstractIntegrationTest.java @@ -0,0 +1,23 @@ +package com.java.baeldung.spring.test; + +import com.github.seregamorph.testsmartcontext.SmartDirtiesContextTestExecutionListener; +import org.junit.jupiter.api.extension.ExtendWith; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.TestExecutionListeners; +import org.springframework.test.context.junit.jupiter.SpringExtension; + +// Avoid putting @DirtiesContext on the integration test super class +// @DirtiesContext +// Use SmartDirtiesContextTestExecutionListener instead +@ContextConfiguration(classes = { + SampleBeanTestConfiguration.class +}) +@ActiveProfiles("test") +@TestExecutionListeners(listeners = { + SmartDirtiesContextTestExecutionListener.class, +}, mergeMode = TestExecutionListeners.MergeMode.MERGE_WITH_DEFAULTS) +@ExtendWith(SpringExtension.class) +public abstract class AbstractIntegrationTest { + +} diff --git a/testing-modules/spring-testing-4/src/test/java/com/java/baeldung/spring/test/AbstractWebIntTest.java b/testing-modules/spring-testing-4/src/test/java/com/java/baeldung/spring/test/AbstractWebIntTest.java new file mode 100644 index 000000000000..487457859b64 --- /dev/null +++ b/testing-modules/spring-testing-4/src/test/java/com/java/baeldung/spring/test/AbstractWebIntTest.java @@ -0,0 +1,20 @@ +package com.java.baeldung.spring.test; + +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.TestPropertySource; + +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) +@EnableAutoConfiguration +@ContextConfiguration(classes = { + PostgresTestConfiguration.class, + DataSourceTestConfiguration.class, + ArticlesController.class +}) +@TestPropertySource(properties = { + "parameter = value" +}) +public abstract class AbstractWebIntTest extends AbstractIntegrationTest { + +} diff --git a/testing-modules/spring-testing-4/src/test/java/com/java/baeldung/spring/test/DataSourceTestConfiguration.java b/testing-modules/spring-testing-4/src/test/java/com/java/baeldung/spring/test/DataSourceTestConfiguration.java new file mode 100644 index 000000000000..88554c81196b --- /dev/null +++ b/testing-modules/spring-testing-4/src/test/java/com/java/baeldung/spring/test/DataSourceTestConfiguration.java @@ -0,0 +1,64 @@ +package com.java.baeldung.spring.test; + +import com.github.seregamorph.testsmartcontext.jdbc.LateInitDataSource; +import com.zaxxer.hikari.HikariDataSource; +import org.postgresql.Driver; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.testcontainers.containers.PostgreSQLContainer; + +import javax.sql.DataSource; + +@Configuration +public class DataSourceTestConfiguration { + + private static final Logger logger = LoggerFactory.getLogger(DataSourceTestConfiguration.class); + +// Avoid creating TestContainer objects which are not spring-managed beans +// @Bean +// public DataSource dataSource() { +// // not a manageable bean! +// var container = new PostgreSQLContainer("postgres:9.6"); +// container.start(); +// return createDataSource("main", container); +// } + + @Bean + public DataSource dataSource(PostgreSQLContainer postgres) { + return createDataSource("main", postgres); + } + + private static DataSource createDataSource(String name, PostgreSQLContainer postgres) { + // todo schema migrations, test data insertion, etc. + if (postgres.isRunning()) { + // already running - create direct dataSource + logger.info("Eagerly initializing pool {}", name); + return createHikariDataSourceForContainer(name, postgres); + } else { + // initialize lazily on first getConnection + logger.info("Pool {} will be initialized lazily", name); + return new LateInitDataSource(name, () -> { + logger.info("Starting container for pool {}", name); + postgres.start(); + return createHikariDataSourceForContainer(name, postgres); + }); + } + } + + private static HikariDataSource createHikariDataSourceForContainer(String name, PostgreSQLContainer container) { + var hikariDataSource = new HikariDataSource(); + hikariDataSource.setUsername(container.getUsername()); + hikariDataSource.setPassword(container.getPassword()); + hikariDataSource.setMinimumIdle(0); + hikariDataSource.setMaximumPoolSize(50); + hikariDataSource.setIdleTimeout(10000); + hikariDataSource.setConnectionTimeout(10000); + hikariDataSource.setAutoCommit(true); + hikariDataSource.setPoolName(name); + hikariDataSource.setDriverClassName(Driver.class.getName()); + hikariDataSource.setJdbcUrl(container.getJdbcUrl()); + return hikariDataSource; + } +} diff --git a/testing-modules/spring-testing-4/src/test/java/com/java/baeldung/spring/test/PostgresTestConfiguration.java b/testing-modules/spring-testing-4/src/test/java/com/java/baeldung/spring/test/PostgresTestConfiguration.java new file mode 100644 index 000000000000..8ba2461e885c --- /dev/null +++ b/testing-modules/spring-testing-4/src/test/java/com/java/baeldung/spring/test/PostgresTestConfiguration.java @@ -0,0 +1,25 @@ +package com.java.baeldung.spring.test; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.testcontainers.containers.PostgreSQLContainer; +import org.testcontainers.utility.DockerImageName; + +@Configuration +public class PostgresTestConfiguration { + + private static PostgreSQLContainer postgres; + + // override destroy method to empty to avoid closing docker container + // bean on closing spring context + @Bean(destroyMethod = "") + public PostgreSQLContainer postgresContainer() { + synchronized (PostgresTestConfiguration.class) { + if (postgres == null) { + postgres = new PostgreSQLContainer<>(DockerImageName.parse("postgres:14") + .asCompatibleSubstituteFor("postgres")); + } + return postgres; + } + } +} diff --git a/testing-modules/spring-testing-4/src/test/java/com/java/baeldung/spring/test/SampleBean.java b/testing-modules/spring-testing-4/src/test/java/com/java/baeldung/spring/test/SampleBean.java new file mode 100644 index 000000000000..0e502457665f --- /dev/null +++ b/testing-modules/spring-testing-4/src/test/java/com/java/baeldung/spring/test/SampleBean.java @@ -0,0 +1,8 @@ +package com.java.baeldung.spring.test; + +public class SampleBean { + + public String getValue() { + return "default"; + } +} diff --git a/testing-modules/spring-testing-4/src/test/java/com/java/baeldung/spring/test/SampleBeanTestConfiguration.java b/testing-modules/spring-testing-4/src/test/java/com/java/baeldung/spring/test/SampleBeanTestConfiguration.java new file mode 100644 index 000000000000..3d7e38e8bbfe --- /dev/null +++ b/testing-modules/spring-testing-4/src/test/java/com/java/baeldung/spring/test/SampleBeanTestConfiguration.java @@ -0,0 +1,13 @@ +package com.java.baeldung.spring.test; + +import org.springframework.boot.test.context.TestConfiguration; +import org.springframework.context.annotation.Import; + +@Import({ + SampleBean.class, + SampleService.class +}) +@TestConfiguration +public class SampleBeanTestConfiguration { + +} diff --git a/testing-modules/spring-testing-4/src/test/java/com/java/baeldung/spring/test/SampleService.java b/testing-modules/spring-testing-4/src/test/java/com/java/baeldung/spring/test/SampleService.java new file mode 100644 index 000000000000..8e7837e1bb23 --- /dev/null +++ b/testing-modules/spring-testing-4/src/test/java/com/java/baeldung/spring/test/SampleService.java @@ -0,0 +1,32 @@ +package com.java.baeldung.spring.test; + +import jakarta.annotation.PreDestroy; + +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; + +public class SampleService { + + private final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(16); + + private final SampleBean sampleBean; + + public SampleService(SampleBean sampleBean) { + this.sampleBean = sampleBean; + } + + public void scheduleNow(Runnable command, long periodSeconds) { + scheduler.scheduleAtFixedRate(command, 0L, periodSeconds, TimeUnit.SECONDS); + } + + public String getValue() { + return sampleBean.getValue(); + } + + // to avoid thread leakage in test execution + @PreDestroy + public void shutdown() { + scheduler.shutdown(); + } +} diff --git a/testing-modules/spring-testing-4/src/test/java/com/java/baeldung/spring/test/Test1IntTest.java b/testing-modules/spring-testing-4/src/test/java/com/java/baeldung/spring/test/Test1IntTest.java new file mode 100644 index 000000000000..bc41f3663522 --- /dev/null +++ b/testing-modules/spring-testing-4/src/test/java/com/java/baeldung/spring/test/Test1IntTest.java @@ -0,0 +1,45 @@ +package com.java.baeldung.spring.test; + +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class Test1IntTest extends AbstractIntegrationTest { + + @Autowired + private SampleBean rootBean; + + @Test + public void test() { + System.out.println("Test1IT.test " + rootBean); + assertEquals("default", rootBean.getValue()); + } + + @Nested + public class NestedIT { + + @Autowired + private SampleBean nestedBean; + + @Test + public void nested() { + System.out.println("Test1IT.NestedTest.test " + nestedBean); + } + + @Nested + public class DeeplyNestedIT { + + @Autowired + private SampleService sampleService; + + @Test + public void deeplyNested() { + assertEquals("default", sampleService.getValue()); + System.out.println("Test1IT.NestedTest.DeeplyNestedTest.deeplyNested " + sampleService); + } + } + } + +} diff --git a/testing-modules/spring-testing-4/src/test/java/com/java/baeldung/spring/test/Test2IntTest.java b/testing-modules/spring-testing-4/src/test/java/com/java/baeldung/spring/test/Test2IntTest.java new file mode 100644 index 000000000000..fe0f1ed62315 --- /dev/null +++ b/testing-modules/spring-testing-4/src/test/java/com/java/baeldung/spring/test/Test2IntTest.java @@ -0,0 +1,18 @@ +package com.java.baeldung.spring.test; + +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class Test2IntTest extends AbstractIntegrationTest { + + @Autowired + private SampleBean rootBean; + + @Test + public void test() { + System.out.println("Test2IT.test " + rootBean); + assertEquals("default", rootBean.getValue()); + } +} diff --git a/testing-modules/spring-testing-4/src/test/java/com/java/baeldung/spring/test/Test3IntTest.java b/testing-modules/spring-testing-4/src/test/java/com/java/baeldung/spring/test/Test3IntTest.java new file mode 100644 index 000000000000..f0296a973fd2 --- /dev/null +++ b/testing-modules/spring-testing-4/src/test/java/com/java/baeldung/spring/test/Test3IntTest.java @@ -0,0 +1,29 @@ +package com.java.baeldung.spring.test; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.mock.mockito.MockBean; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.Mockito.when; + +public class Test3IntTest extends AbstractIntegrationTest { + + @Autowired + private SampleService sampleService; + + // declaring MockBean leads to a separate spring context for this test class + @MockBean + private SampleBean sampleBean; + + @BeforeEach + public void setUp() { + when(sampleBean.getValue()).thenReturn("mock"); + } + + @Test + public void test() { + assertEquals("mock", sampleBean.getValue()); + } +} diff --git a/testing-modules/spring-testing-4/src/test/java/com/java/baeldung/spring/test/Test4IntTest.java b/testing-modules/spring-testing-4/src/test/java/com/java/baeldung/spring/test/Test4IntTest.java new file mode 100644 index 000000000000..cb2f4b6607aa --- /dev/null +++ b/testing-modules/spring-testing-4/src/test/java/com/java/baeldung/spring/test/Test4IntTest.java @@ -0,0 +1,42 @@ +package com.java.baeldung.spring.test; + +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.context.annotation.Bean; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.TestPropertySource; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; +import org.springframework.web.context.WebApplicationContext; + +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.MOCK) +@ContextConfiguration(classes = { + Test4IntTest.Configuration.class +}) +@TestPropertySource(properties = { + "parameter = value" +}) +public class Test4IntTest extends AbstractIntegrationTest { + + @Autowired + private MockMvc mockMvc; + + @Test + public void test404() throws Exception { + mockMvc.perform(get("/article")) + .andExpect(status().isNotFound()); + } + + public static class Configuration { + + @Bean + public MockMvc mockMvc(WebApplicationContext webApplicationContext) { + var builder = MockMvcBuilders.webAppContextSetup(webApplicationContext); + return builder.build(); + } + } +} diff --git a/testing-modules/spring-testing-4/src/test/java/com/java/baeldung/spring/test/Test5IntTest.java b/testing-modules/spring-testing-4/src/test/java/com/java/baeldung/spring/test/Test5IntTest.java new file mode 100644 index 000000000000..46b83918afe3 --- /dev/null +++ b/testing-modules/spring-testing-4/src/test/java/com/java/baeldung/spring/test/Test5IntTest.java @@ -0,0 +1,31 @@ +package com.java.baeldung.spring.test; + +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.web.client.TestRestTemplate; +import org.springframework.boot.test.web.server.LocalServerPort; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class Test5IntTest extends AbstractWebIntTest { + + // will inject actual dynamic port + @LocalServerPort + private int port; + + @Autowired + private TestRestTemplate testRestTemplate; + + @Test + public void shouldInjectLocalServerPort() { + assertTrue(port > 0, "port is not initialized"); + } + + @Test + public void testGetArticleReturns200() { + var entity = testRestTemplate.getForEntity("/articles/1", String.class); + assertEquals(200, entity.getStatusCode().value()); + assertEquals("Content 1", entity.getBody()); + } +} diff --git a/testing-modules/spring-testing-4/src/test/java/com/java/baeldung/spring/test/Test6IntTest.java b/testing-modules/spring-testing-4/src/test/java/com/java/baeldung/spring/test/Test6IntTest.java new file mode 100644 index 000000000000..29f8c8d7622c --- /dev/null +++ b/testing-modules/spring-testing-4/src/test/java/com/java/baeldung/spring/test/Test6IntTest.java @@ -0,0 +1,19 @@ +package com.java.baeldung.spring.test; + +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.web.client.TestRestTemplate; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class Test6IntTest extends AbstractWebIntTest { + + @Autowired + private TestRestTemplate testRestTemplate; + + @Test + public void testGetArticlesReturns404() { + var entity = testRestTemplate.getForEntity("/articles", String.class); + assertEquals(404, entity.getStatusCode().value()); + } +} diff --git a/testing-modules/spring-testing-4/src/test/java/com/java/baeldung/spring/test/Test7IntTest.java b/testing-modules/spring-testing-4/src/test/java/com/java/baeldung/spring/test/Test7IntTest.java new file mode 100644 index 000000000000..2aa9d5904de3 --- /dev/null +++ b/testing-modules/spring-testing-4/src/test/java/com/java/baeldung/spring/test/Test7IntTest.java @@ -0,0 +1,21 @@ +package com.java.baeldung.spring.test; + +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; + +public class Test7IntTest extends AbstractIntegrationTest { + + @Test + public void test() { + System.out.println("Test7IT.test"); + } + + @Nested + public class NestedIT { + + @Test + public void nested() { + System.out.println("Test7IT.NestedTest.test"); + } + } +} diff --git a/testing-modules/spring-testing-4/src/test/java/com/java/baeldung/spring/test/Test8IntTest.java b/testing-modules/spring-testing-4/src/test/java/com/java/baeldung/spring/test/Test8IntTest.java new file mode 100644 index 000000000000..bc02344573ca --- /dev/null +++ b/testing-modules/spring-testing-4/src/test/java/com/java/baeldung/spring/test/Test8IntTest.java @@ -0,0 +1,19 @@ +package com.java.baeldung.spring.test; + +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.web.client.TestRestTemplate; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class Test8IntTest extends AbstractWebIntTest { + + @Autowired + private TestRestTemplate testRestTemplate; + + @Test + public void testPostArticlesReturns405() { + var entity = testRestTemplate.postForEntity("/articles/1", new byte[0], String.class); + assertEquals(405, entity.getStatusCode().value()); + } +} diff --git a/testing-modules/spring-testing-4/src/test/resources/spring.properties b/testing-modules/spring-testing-4/src/test/resources/spring.properties new file mode 100644 index 000000000000..7285e78ee762 --- /dev/null +++ b/testing-modules/spring-testing-4/src/test/resources/spring.properties @@ -0,0 +1,2 @@ +# limit the spring-test context cache max size (default 32) +spring.test.context.cache.maxSize=4