Add reference documentation chapter on 'Testing'.

Resolves gh-102.
This commit is contained in:
John Blum
2021-02-01 22:05:00 -08:00
committed by John Blum
parent 555824ff83
commit 237cf8ec3e
3 changed files with 254 additions and 3 deletions

View File

@@ -13,7 +13,7 @@ _Table of Contents_
3. <<geode-auto-configuration-disable>>
4. <<geode-gemfire-switch>>
5. <<geode-cluster-configuration-bootstrapping>>
6. <<geode-testing>>
6. <<geode-testing-support>>
7. <<geode-examples>>
8. <<references>>
@@ -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

View File

@@ -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<User, String> { }
----
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 <<geode-testing-unit>>, 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<User, String> { }
----
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.

View File

@@ -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[]