diff --git a/spring-geode-docs/src/docs/asciidoc/_includes/appendix.adoc b/spring-geode-docs/src/docs/asciidoc/_includes/appendix.adoc index 1edd5800..c3970426 100644 --- a/spring-geode-docs/src/docs/asciidoc/_includes/appendix.adoc +++ b/spring-geode-docs/src/docs/asciidoc/_includes/appendix.adoc @@ -13,7 +13,7 @@ _Table of Contents_ 3. <> 4. <> 5. <> -6. <> +6. <> 7. <> 8. <> @@ -555,7 +555,7 @@ Congratulations! You just started a small {geode-name} cluster, with 3 members, It is pretty simple to build and run a Spring Boot, {geode-name}, `ClientCache` application that connects to this cluster. Simply include and use Spring Boot for {geode-name}, ;-). -[[geode-testing]] +[[geode-testing-support]] === Testing https://github.com/spring-projects/spring-test-data-geode[Spring Test for {geode-name}] is a new, soon to be released diff --git a/spring-geode-docs/src/docs/asciidoc/_includes/testing.adoc b/spring-geode-docs/src/docs/asciidoc/_includes/testing.adoc new file mode 100644 index 00000000..e3f9bd9a --- /dev/null +++ b/spring-geode-docs/src/docs/asciidoc/_includes/testing.adoc @@ -0,0 +1,251 @@ +[[geode-testing]] +== Testing +:gemfire-name: {pivotal-gemfire-name} +:geode-name: {apache-geode-name} +:stdg-website: https://github.com/spring-projects/spring-test-data-geode + +Spring Boot for {geode-name} (SBDG), with help from {stdg-website}[Spring Test for {geode-name} (STDG)], offers +first-class support for both _Unit_ & _Integration Testing_ of {geode-name} in your Spring Boot applications. + +TIP: See the Spring Test for Apache Geode (STDG) {stdg-website}/#stdg-in-a-nutshell[documentation] for more details. + +[[geode-testing-unit]] +=== Unit Testing + +_Unit Testing_ with {geode-name} using _mock objects_ in a Spring Boot Test is as simple as declaring the STDG +`@EnableGemFireMockObjects` annotation in your test configuration: + +.Unit Test with {geode-name} using Spring Boot +[source,java] +---- +@SpringBootTest +@RunWith(SpringRunner.class) +public class SpringBootApacheGeodeUnitTest extends IntegrationTestsSupport { + + @Autowired + private UserRepository userRepository; + + @Test + public void saveAndFindUserIsSuccessful() { + + User jonDoe = User.as("jonDoe"); + + assertThat(this.userRepository.save(jonDoe)).isNotNull(); + + User jonDoeFoundById = this.userRepository.findById(jonDoe.getName()).orElse(null); + + assertThat(jonDoeFoundById).isEqualTo(jonDoe); + } + + @SpringBootApplication + @EnableGemFireMockObjects + @EnableEntityDefinedRegions(basePackageClasses = User.class) + static class TestConfiguration { } + +} + +@Getter +@ToString +@EqualsAndHashCode +@RequiredArgsConstructor(staticName = "as") +@Region("Users") +class User { + + @Id + @lombok.NonNull + private String name; + +} + +interface UserRepository extends CrudRepository { } +---- + +While this test class is not a "pure" _Unit Test_, particularly since it bootstraps an actual Spring `ApplicationContext` +using Spring Boot, it does, however, "_mock_" all {geode-name} objects, such as the "_Users_" `Region` declared by the +`User` application entity class, which was annotated with SDG's `@Region` mapping annotation. + +This test class conveniently uses Spring Boot's _auto-configuration_ to auto-configure an {geode-name} `ClientCache` +instance. In addition, SDG's `@EnableEntityDefinedRegions` annotation was used to conveniently create the {geode-name} +"_Users_" `Region` to store instances of `User`. + +Finally, Spring Data's _Repository_ abstraction was used to conveniently perform basic CRUD (e.g. `save`) and simple +(OQL) query (e.g. `findById`) data access operations on the "_Users_" `Region`. + +Even though the {geode-name} objects (e.g. "_Users_" `Region`) are "_mock objects_", you can still perform many of +the data access operations required by your Spring Boot application's business logic in a {geode-name} API agnostic way, +that is, using Spring's powerful programming model and constructs! + +TIP: By extending STDG's `org.springframework.data.gemfire.tests.integration.IntegrationTestSupport` class, +you ensure that all {geode-name} mock objects and resources are properly released after the test class runs, +thereby preventing any interference with downstream tests. + +While STDG tries to {stdg-website}/#mock-regions-with-data[mock the functionality and behavior] for many `Region` +operations, it is simply not pragmatic to mock them all. For example, it would not be practical to mock `Region` +query operations involving complex OQL statements having sophisticated predicates. + +If such functional testing is required, then the test might be better suited as an _Integration Test_. Alternatively, +you can follow the advice in this {stdg-website}/#mocking-unsupported-region-operations[section]. + +In general, STDG provides the following capabilities when mocking {geode-name} objects out-of-the-box: + +* {stdg-website}#mock-object-scope--lifecycle-management[Mock Object Scope & Lifecycle Management] +* {stdg-website}#mock-regions-with-data[Support for Mock Regions with Data] +* {stdg-website}#mock-region-callbacks[Support for Mocking Region Callbacks] +* {stdg-website}#mocking-unsupported-region-operations[Support for Mocking Unsupported Region Operations] + +TIP: See documentation on {stdg-website}/#unit-testing-with-stdg[Unit Testing with STDG] for more details. + +[[geode-testing-integration]] +=== Integration Testing + +_Integration Testing_ with {geode-name} in a Spring Boot Test is as simple as **not** declaring STDG's +`@EnableGemFireMockObjects` annotation in your test configuration. Of course, you may then want to additionally use +SBDG's `@EnableClusterAware` annotation to conditionally detect the presence of a {geode-name} cluster: + +.Using `@EnableClusterAware` in test configuration +[source,java] +---- +@SpringBootApplication +@EnableClusterAware +@EnableEntityDefinedRegions(basePackageClasses = User.class) +static class TestConfiguration { } +---- + +The SBDG `@EnableClusterAware` annotation will conveniently toggle your auto-configured `ClientCache` instance +between local-only mode and client/server. Additionally, it will even push configuration metadata +(e.g. `Region` definitions) up to the server(s) in the cluster required by the application to persist data. + +In most cases, in addition to testing with "_live_" {geode-name} objects (e.g. _Regions_), we also want to test +in a client/server capacity. This unlocks the full capabilities of the {geode-name} data management system +in a Spring context, and gets you as close as possible to production from the comfort of your IDE. + +Building on our example from the section on <>, you can modify the test to use "_live_" +{geode-name} objects in a client/server topology as follows: + +.Integration Test with {geode-name} using Spring Boot +[source,java] +---- +@ActiveProfiles("client") +@RunWith(SpringRunner.class) +@SpringBootTest(properties = "spring.data.gemfire.management.use-http=false") +public class SpringBootApacheGeodeIntegrationTest extends ForkingClientServerIntegrationTestsSupport { + + @BeforeClass + public static void startGeodeServer() throws IOException { + startGemFireServer(TestGeodeServerConfiguration.class); + } + + @Autowired + private UserRepository userRepository; + + @Test + public void saveAndFindUserIsSuccessful() { + + User jonDoe = User.as("jonDoe"); + + assertThat(this.userRepository.save(jonDoe)).isNotNull(); + + User jonDoeFoundById = this.userRepository.findById(jonDoe.getName()).orElse(null); + + assertThat(jonDoeFoundById).isEqualTo(jonDoe); + assertThat(jonDoeFoundById).isNotSameAs(jonDoe); + } + + @SpringBootApplication + @EnableClusterAware + @EnableEntityDefinedRegions(basePackageClasses = User.class) + @Profile("client") + static class TestGeodeClientConfiguration { } + + @CacheServerApplication + @Profile("server") + static class TestGeodeServerConfiguration { + + public static void main(String[] args) { + + new SpringApplicationBuilder(TestGeodeServerConfiguration.class) + .web(WebApplicationType.NONE) + .profiles("server") + .build() + .run(args); + } + } +} + +@Getter +@ToString +@EqualsAndHashCode +@RequiredArgsConstructor(staticName = "as") +@Region("Users") +class User { + + @Id + @lombok.NonNull + private String name; + +} + +interface UserRepository extends CrudRepository { } +---- + +The application client/server-based _Integration Test_ class extend STDG's +`org.springframework.data.gemfire.tests.integration.ForkingClientServerIntegrationTestsSupport` class. +This ensures that all {geode-name} objects and resources are properly cleaned up after the test class runs. In addition, +it coordinates the client & server components of the test (e.g. connecting the client to the server using a random port). + +The server is started in a `@BeforeClass` setup method: + +.Start the {geode-name} server +[source,java] +---- +class SpringBootApacheGeodeIntegrationTest extends ForkingClientServerIntegrationTestsSupport { + + @BeforeClass + public static void startGeodeServer() throws IOException { + startGemFireServer(TestGeodeServerConfiguration.class); + } +} +---- + +STDG allows you to configure the server with Spring config, specified in the `TestGeodeServerConfiguration` class. +The Java class needs to provide a `main` method. It uses the `SpringApplicationBuilder` to bootstrap the {geode-name} +`CacheServer` application. + +.{geode-name} server configuration +[source,java] +---- +@CacheServerApplication +@Profile("server") +static class TestGeodeServerConfiguration { + + public static void main(String[] args) { + + new SpringApplicationBuilder(TestGeodeServerConfiguration.class) + .web(WebApplicationType.NONE) + .profiles("server") + .build() + .run(args); + } +} +---- + +In this case, we provide very minimal configuration since the configuration is determined and pushed up to the server +by the client. For example, we do not need to explicitly create the "_Users_" `Region` on the server-side since it is +implicitly handled for you by the SBDG/STDG frameworks from the client. + +We take advantage of Spring _Profiles_ in the test setup to distinguish between the client & server configuration. Keep +in mind that the test is the "client" in this arrangement. + +The STDG framework is doing as the supporting class states, "forking" the Spring Boot-based, {geode-name} `CacheServer` +application in a separate JVM process. Subsequently, the STDG framework will stop the server upon completion of +the tests in the test class. + +Of course, you are free to start your server(s) or cluster however you choose. STDG simply and conveniently provides +this capability for you since it is a common concern. + +This test class is very simple and much more complex test scenarios can be easily handled by STDG. + +TIP: Review SBDG's test suite to witness the full power and functionality of the STDG framework for yourself. + +NOTE: See documentation on {stdg-website}/#integration-testing-with-stdg[Integration Testing with STDG] +for more details. diff --git a/spring-geode-docs/src/docs/asciidoc/index.adoc b/spring-geode-docs/src/docs/asciidoc/index.adoc index 9a998c8b..35321a2b 100644 --- a/spring-geode-docs/src/docs/asciidoc/index.adoc +++ b/spring-geode-docs/src/docs/asciidoc/index.adoc @@ -429,7 +429,6 @@ repositories { ---- endif::[] - include::{include-dir}/clientcache-applications.adoc[] include::{include-dir}/configuration-auto.adoc[] include::{include-dir}/configuration-declarative.adoc[] @@ -444,6 +443,7 @@ include::{include-dir}/data.adoc[] include::{include-dir}/data-serialization.adoc[] include::{include-dir}/logging.adoc[] include::{include-dir}/security.adoc[] +include::{include-dir}/testing.adoc[] include::{include-dir}/geode-api-ext.adoc[] include::{include-dir}/actuator.adoc[] include::{include-dir}/session.adoc[]