image:https://api.travis-ci.org/spring-projects/spring-boot-data-geode.svg?branch=master["Build Status", link="https://travis-ci.org/spring-projects/spring-boot-data-geode"]

[[about]]
== Spring Test Framework for Apache Geode & Pivotal GemFire

The STDG project is a _Spring Data_ module, building on the core _Spring Framework's_ `TestContext`, used to write
_Unit_ and _Integration Tests_ when building _Spring Data_ for https://geode.apache.org/[Apache Geode]
& https://pivotal.io/pivotal-gemfire[Pivotal GemFire] (SDG) applications.

This project was born from https://spring.io/projects/spring-data-gemfire[_Spring Data for Pivotal GemFire's_]
(https://github.com/spring-projects/spring-data-gemfire[@GitHub])
https://github.com/spring-projects/spring-data-gemfire/tree/2.1.6.RELEASE/src/test/java/org/springframework/data/gemfire/test[test framework].
This _test framework_ is used in SDG's test suite to test the proper function of Apache Geode & Pivotal GemFire
in a _Spring_ context.

For several years now, users have asked for a way to test their Apache Geode & Pivotal GemFire based,
_Spring_ applications reliably and easily, when writing _Unit_ and _Integrations_ tests.

Additionally, STDG was created to consolidate the testing efforts, lessons learned, and knowledge of effectively testing
all Spring for Apache Geode/Pivotal GemFire projects: _Spring Boot for Apache Geode & Pivotal GemFire_ (SBDG)
and _Spring Session for Apache Geode & Pivotal GemFire_ (SSDG).

Eventually, STDG will replace the SDG test classes so that tests and testing efforts are consistent across all Spring
projects for Apache Geode/Pivotal GemFire: SDG, SBDG and SSDG.

This (relatively) **new** project is still under development and will have documentation, examples
and an extensive test suite once complete.

In the meantime, you can review the
https://github.com/spring-projects/spring-boot-data-geode/tree/master/spring-geode-autoconfigure/src/test/java/org/springframework/geode/boot/autoconfigure[test suite for SBDG]
and the https://github.com/spring-projects/spring-session-data-geode/tree/master/spring-session-data-geode/src/test/java/org/springframework/session/data/gemfire[test suite for SSDG]
to get a sense of how this project is used and works.


[[nutshell]]
== STDG in a Nutshell

Until proper documentation has been provided, this very short and simple tutorial will hopefully give you a better idea
of how this project is used.


[[unit-tests]]
=== Unit Testing with STDG

We all write tests, right?  TDD style?  ;-)

As we begin to write tests, we typically start with _Unit Tests_ since they are designed to test the subject
in isolation, without dependencies, to get feedback quickly, i.e. "_Is my logic correct?_"

It common when writing _Unit Tests_ to *mock* the dependencies since the test should assume that the dependencies
"_work as designed_".  During _Unit Testing_, it does not matter whether or not the dependencies actually work
as expected (that is the purpose of _Integration Tests_), just that they have a contract and our application components,
the "_Subject Under Test_" (SUT), honors that contract and uses the external dependencies correctly. Essentially we
are asserting that the interactions between our application components and external dependencies is correct
and the results lead to the desired outcome.

Well, it is, or should be, no different when you are using Apache Geode or Pivotal GemFire.

For instance, you might want to mock that your _Data Access Object_ (DAO) performs the proper interactions on
a GemFire/Geode Region, performing the right CRUD operations, making sure the right (OQL) Queries are executed
for the Use Case or business function and workflow being performed by the application.

In this case, we don't care whether the Region is real or not, or that an OQL Query is actually well formed and would
execute properly, performantly, returning the correct results.  We would "mock" the Regions' behavior in this case
to make sure that our DAO interactions with the Region are correct, that it handles the translation of Exceptions
or other Error conditions, that it transforms values to/from the backend data store (i.e. Region), and so on. That is
how you properly test the subject.

To support _Unit Testing_ with Apache Geode or Pivotal GemFire in a Spring context, STDG provides the
`@EnableGemFireMockObjects` annotation.  If you want to use GemFire/Geode Mock Objects, e.g. a "mock" Region rather
than a "live" Region, than you simply only need to annotate your test configuration with `@EnableGemFireMockObjects`.

For example:

.Unit Test with GemFire/Geode Mock Objects
[source,java]
----
@RunWith(SpringRunner.class)
@ContextConfiguration
class ExampleUnitTestClass {

  // test case methods here

  @EnableGemFireMockObjects
  @ClientCacheApplication
  @EnableEntityDefinedRegions(clientRegionShortcut = ClientRegionShortcut.LOCAL)
  static class TestConfiguration { }

}
----


In the example above, `@EnableGemFireMockObjects` creates "mocks" for the `ClientCache`, all the `Regions` identified
and created by the `@EnableEntityDefinedRegions(..)` annotation, along with all the object GemFire/Geode object types.
There are no "live" GemFire/Geode objects when "mocking" is enabled.

Here is 1
https://github.com/spring-projects/spring-test-data-geode/blob/master/spring-data-geode-test/src/test/java/org/springframework/data/gemfire/MockClientCacheApplicationIntegrationTests.java[example]
of a concrete _Unit Test_ in action, using STDG's `@EnableGemFireMockObjects` annotation.

It really is that simple!

TIP: Mocking GemFire/Geode objects outside a Spring context is possible, but beyond the scope of this guide
for the time being.

[[unit-tests-mock-region-data]]
==== Mock Regions with Data

While implementing a fully capable GemFire/Geode Region would defeat the purpose of Mocking and Unit Testing in general,
it is desirable to sometimes perform basic Region data access operations, such as `get` and `put`, with small quantities
of data and emulate the same effects.

As such, with STDG it is currently possible to perform the following Region data access operations:

* `containsKey(key)`
* `get(key)`,
* `getEntry(key)`,
* `invalidate(key)`,
* `put(key, value)`
* `size()`,

The "mock" Region will function and behave similarly to an actual GemFire/Geode Region involving these
data access operations.

By way of example, this means you can do things like the following in a Unit Test with a "mock" Region:

.Basic data access operations on a mocked Region
[source,java]
----
@RunWith(SpringRunner.class)
@ContextConfiguration
class MyGeodeMockRegionUnitTests {

  @Resource(name = "Example")
  private Region<?, ?>  mockRegion;

  @Test
  public void simpleGetAndPutRegionOpsWork() {

      mockRegion.put(1, "test");

      assertThat(mockRegion).containsKey(1);
      assertThat(mockRegion.get(1)).isEqualTo("test");
  }

  @ClientCacheApplication
  @EnableGemFireMockObjects
  static class TestConfiguration {

    @Bean("Example")
    ClienRegionFactoryBean mockRegion(GemFireCache gemfireCache) {

        ClientRegionFactoryBean mockRegion = new ClientRegionFactoryBean();

        mockRegion.setCache(gemfireCache);

        return mockRegion;
    }
  }
}
----

Of course, you can also perform similar Region data access operations using the _Spring Data Repository_ abstraction
instead. The benefit of _Spring Data's_ _Repository_ abstraction is that it insulates your application from Apache Geode
and hides the fact that you are interfacing with an Region under-the-hood by using the proper _Data Access Object_ (DAO)
pattern.

For example, you can "mock" a Region and `put`/`get` data using a _Spring Data Repository_ for the Region
as demonstrated by the following code.

Given a `Customer` application domain object annotated with the `@Region` mapping annotation:

.Customer
[source,java]
----
@Region("Customers")
class Customer {

    @Id
    private Long id;

    ...
}
----

Along with a SD _Repository_ for `Customers`:

.CustomerRepository
[source,java]
----
interface CustomerRepository extends CrudRepository<Customer, Long> { ... }
----

Then you can write a test class like the following, still using a "mock" Region to `put` and `get` actual data:

.Spring Data Repository on a mocked Region
[source,java]
----
@RunWith(SpringRunner.class)
@ContextConfiguration
class MySpringDataRepositoryWithMockRegionUnitTests {

    @Autowired
    private CustomerRepository customerRepository;

    @Test
    public void simpleRepositoryCrudOpsWork() {

        Customer jonDoe = ...;

        customerRepository.save(jonDoe);

        assertThat(customerRepository.existsById(jonDoe.getId()).isTrue();
        assertThat(customerRepository.findById(jonDoe.getId()).orElse(null)).isEqualTo(jonDoe);
    }

    @ClientCacheApplication
    @EnableEntityDefinedRegions(basePackageClasses = Customer.class)
    @EnableGemfireRepositories(basePackageClasses = CustomerRepository.class)
    static class TestConfiguration { ... }

}
----

Even though you are using _Spring Data Repositories_ and the `@EnableEntityDefinedRegions` annotation (perhaps;
yes these components still work with Mocks and mock data), you can still autowire/inject the Region and access
it directly in the same test class:

.Accessing the mock Region directly in the SD Repository test
[source,java]
----
@RunWith(SpringRunner.class)
@ContextConfiguration
class MySpringDataRepositoryWithMockRegionUnitTests {

    @Autowired
    private CustomerRepository customerRepository;

    @Resource
    Region<Long, Customer> customers;

    @Test
    public void simpleRepositoryCrudOpsWork() { ... }

    @Test
    public void customerRegionOpsWorkToo() {

        Customer janeDoe = ...;

        customers.put(janeDoe.getId(), janeDoe);

        assertThat(customers).containsKey(janeDoe.getId());
        assertThat(customers.get(janeDoe.getId())).isEqualTo(janeDoe);
        assertThat(customerRepository.findById(janeDoe.getId()).orElse(null)).isEqualTo(janeDoe);
    }
}
----

For clarification, obviously many of the Region functions and behaviors are not implemented, like persistence,
or overflow to disk, distribution, replication, eviction, expiration, etc.  If you find you need to test your
application with these behaviors and functions, then it would clearly be better suited as an actual Integration Test
at that point.

[[unit-tests-mock-region-callbacks]]
==== Mock Region Callbacks

A relatively *new* feature in STDG is the ability to register and invoke cache (Region) callbacks, such as
`CacheListeners`, or a `CacheLoader` or a `CacheWriter`.

Cache callbacks like `CacheListeners` or `CacheLoader/Writers` are user-defined, application objects that can be
registered with a Region to listen for events, load data on cache misses, or write the Region's data to a backend,
external data source.

It is sometimes useful when testing to partially mock some dependencies (a.k.a. collaborators; e.g. Regions)
while using live objects for others (e.g. cache callbacks like a `CacheListener`).

The reason behind this testing strategy is that some objects are mostly infrastructure related (e.g. a Region),
and not the primary focus of the test, while other objects are still very much tied to the application's function
and behavior (e.g. a `CacheListener` or a `CacheLoader`), i.e. they are part of the application's workflow.

As such, STDG not only allows you to register `CacheListeners` and `CacheLoaders/Writers` (you could do so before
as well), but will now additionally invoke the Listeners, Loader and Writer at the appropriate point in the Region
operation's process flow.

For example, a registered `CacheWriter` is invoked before the object (value) is put into the Region using the
`Region.put(key, value)` operation.  This is exactly what GemFire/Geode does in order to ensure consistency with
the backend, external data source.  If the `CacheWriter` throws an exception during 1 of it's event handler callbacks
(e.g. `beforeCreate(:EntryEvent<K, V>)` then it will prevent the object from being inserted into the Region.
The same behavior is true for a STDG mock Region.

By way of example, let's demonstrate with a `CacheLoader`:

.Application `CacheLoader` on mock Region
[source,java]
----
@RunWith(SpringRunner.class)
@ContextConfiguration
class MyMockRegionWithCacheLoaderUnitTests {

  @Resource(name = "Example")
  private Region example;

  @Test
  public void cacheLoaderWorks() {

    assertThat(example.get("one")).isEqualTo(1);
    assertThat(example.get("two")).isEqualTo(2);
    ...
  }

  @ClientCacheApplication
  @EnableGemFireMockObjects
  static class TestConfiguration {

    @Bean
    ClienRegionFactoryBean exampleRegion(GemFireCache gemfireCache) {

      ClientRegionFactoryBean exampleRegion = new ClientRegionFactoryBean();

      exampleRegion.setCache(gemfireCache);
      exampleRegion.setCacheLoader(counterCacheLoader());

      return exampleRegion;
    }
  }

  @Bean
  CacheLoader<Object, Object> counterCacheLoader() {

    AtomicInteger counter = new AtomicInteger(0);

    return new CacheLoader<>() {

      @Override
      public Object load(LoaderHelper<Object, Object> helper) {
        return counter.incrementAndGet();
      }
    };
  }
}
----

As seen in the test above, performing a `Region.get(key)` for keys "one" and "two" on an initially empty Region
will result in cache misses, which will then invoke the registered, application "counter" `CacheLoader` to supply
the value for the requested keys.

You can register a `CacheWriter` along with 1 or more `CacheListeners` and they will be invoked, too.


[[integration-testing]]
=== Integration Testing with STDG

You should write many more _Unit Tests_ than _Integration Tests_ to get reliable and fast feedback.  This is a
no brainer and software development 101.

However, _Unit Tests_ do not completely take the place of _Integration Tests_, either.  Both are necessary, as are
perhaps other forms of testing (e.g. Functional Testing, Acceptance Testing, Smoke Testing, Performance Testing,
Concurrency Testing, etc).

For instance, you should verify that the (OQL) Query you just constructed, maybe even generated, is well-formed
and yields the desired results, is performant, and all that jazz.  You can only reliably do that by executing
the (OQL) Query against an actual GemFire/Geode Region with a properly constructed and deliberate data set.

This sort _Integration Test_ does not have a complex arrangement, and can be performed simply by removing
or disabling the `@EnableGemFireMockObjects` annotation in our previous example above.

However, other forms of _Integration Testing_ might require a more complex arrangement,
such as client/server integration tests.

For instance, you may want to test that a client receives all the events from the server to which it has explicitly
registered interests.  For this type of test, you need to have a (1 or more) GemFire/Geode server(s) running,
and perhaps even a few clients.

Ideally, you want to fork a GemFire/Geode server JVM process in the _Integration Test_ class requiring
a server instance.

Once again, STDG comes to the rescue.

For example:

.Client/Server Integration Test
[source,java]
----
@RunWith(SpringRunner.class)
@ContextConfiguration(classes = GeodeClientTestConfiguration.class)
class ExampleIntegrationTestClass extends ForkingClientServerIntegrationTestsSupport {

  @BeforeClass
  public static void startGemFireServer() {
    startGemFireSever(GeodeServerTestConfiguration.class);
  }

  // test case method here

  @CacheServerApplication
  @EnableEntityDefinedRegions
  static class GeodeServerTestConfiguration {

    public static void main(String[] args) {

        AnnotationConfigApplicationContext applicationContext =
          new AnnotationConfigApplicationContext(GeodeServerTestConfiguration.class);

        applicationContext.registerShutdownHook();
    }
  }

  @ClientCacheApplication
  @EnableEntityDefinedRegions
  static class GeodeClientTestConfiguration { }

}
----

First we extend the STDG provided `ForkingClientServerIntegrationTestsSupport` class.  Then, we define a JUnit
`@BeforeClass` static setup method to fork our GemFire/Geode JVM process using the `GeodeServerTestConfiguration.class`
specifying exactly how the server should be configured and finally we create the matching `GeodeClientTestConfiguration`
class to configure and bootstrap our JUnit, Spring `TestContext` based test, which acts as the client.

STDG takes care of coordinating the client & server, using random connection ports, etc.  You simply just need to
provide the configuration of the client and server as required by your application and test case(s).

Here is 1
https://github.com/spring-projects/spring-boot-data-geode/blob/master/spring-geode-autoconfigure/src/test/java/org/springframework/geode/boot/autoconfigure/security/ssl/AutoConfiguredSslIntegrationTests.java[example]
of a concrete client/server _Integration Test_ extending STDG's `ForkingClientServerIntegrationTestsSupprt` class.

Notice, too, that I am using SDG's
https://docs.spring.io/spring-data/geode/docs/current/reference/html/#bootstrap-annotation-config[Annotation-based configuration model]
(e.g. `CacheServerApplication`, `@EnableEntityDefinedRegions`) to make the GemFire/Geode configuration even easier.

If you are using SBDG with this project, then some of the annotations are not even required (e.g. `ClientCacheApplication`).

When SBDG & STDG are combined, the power you have is quite extensive.

NOTE: Through the _Integration Test_ support provided by and in STDG is relatively simple, this is also not quite yet
the ideal way for writing client/sever _Integration Tests_.  Eventually, we want to include an annotation, something
like `@ClientServerIntegrationTest(serverConfigClass = GeodeServerTestConfiguration.class)`, the equivalent to
`@EnableGemFireMockObjects` for _Unit Testing_, to make configuration and testing of client/server applications
that much easier.  See https://github.com/spring-projects/spring-test-data-geode/issues/9[Issue #9] for more details.
This feature would be loosely based on, and similar to,
_Spring Boot_ https://docs.spring.io/spring-boot/docs/current/reference/html/boot-features-testing.html[Testing]
with _Test Slices_.


[[testing-logging-behavior]]
=== Asserting Logging Behavior

It is sometimes necessary or useful to write tests to assert an application's logging behavior.

For instance, if your application needs to log an event that occurred, output configuration meta-data on startup,
alert a user to some system event such as low memory, out of disk space, or a temporary network outage, or whatever
the case might be, it is useful to assert that your application logs an appropriate message.

But, how do you assert that certain log events with an appropriate log message has been made by the application
when the conditions constituting the log event have been arranged?

Now, STDG provides the capability to 1) assert that your application, or an application component, made a log event
at the appropriate moment and 2) that the log message communicates enough contextual-based information to be useful
to the user of your application.

To do this, STDG provides the `org.springframework.data.geode.tests.logging.slf4j.logback.TestAppender` class.

This Log Appender can be used when your application logging framework is configured with _Logback_ as the provider.

You declare the `TestAppender` in a `logback.xml` configuration file as follows:

.logback.xml configuration file
[source,xml]
----
<appender name="testAppender" class="org.springframework.data.gemfire.tests.logging.slf4j.logback.TestAppender">
    <encoder>
        <pattern>TEST - %m%n</pattern>
    </encoder>
</appender>
----

Then, the `TestAppender` can be used by registering it with a `Logger`:

.Logger using the TestAppender
[source,xml]
----
<logger name="example.app.net.service.NetworkService" level="WARN">
    <appender-ref ref="testAppender"/>
</logger>
----

For example, assume your application's `NetworkService` class uses the named `Logger` to log network events,
e.g. a DDoS attack:

.Application component with logging
[source,java]
----
@Service
class NetworkService {

    private final Logger logger = LoggerFactory.getLogger(NetworkService.class);

    void processDenialOfServiceAttack(NetworkEvent event) {

        logger.warn("A DDoS attack occured at {} from IP Address {}", event.getTime(), event.getIpAddress());

        // process the network event

        logger.warn("Another log message");
    }

    void processLoginRequest(LoginRequest request) {

        logger.info("User {} is attepting to login", request.getUser().getName());

        // process login request
    }
}
----

Then, it is a simple matter to test the logging behavior of your application by doing:

.Test logging behavior of the NetworkService class
[source,java]
----
class NetworkServiceUnitTests {

  private static TestAppender testAppender = TestAppender.getInstance();

  private NetworkService service;

  @Before
  public void setup() {
    this.service = new NetworkService();
  }

  @Test
  public void processDenialOfServiceAttackLogsNetworkEvent() {

    NetworkEvent event = ...;

    this.service.processDenialOfServiceAttack(event);

    assertThat(testAppender.lastLogMessage())
      .isEqualTo("A DDoS attack occured at 2019-07-02 19:39:15 from IP Address 10.22.101.16");

    assertThat(testAppender.lastLogMessage())
      .isEqualTo("Another log message");

    assertThat(testAppender.lastLogMessage()).isNull();
  }

  @Test
  public void processLoginRequestDoesNotLogAnyMessageWithLogLevelSetToWarn() {

      LoginRequest request = ...;

      this.service.processLoginRequest(request);

      assertThat(testAppender.lastLogMessage()).isNull();
  }
}
----

You may also clear any remaining, pending log messages from the in-memory queue (`Stack`)
by calling `TestAppender.clear()`.

All log message recorded by the `TestAppender` are stored from the most recent log event to the earliest log event.
Successively calling `TestAppender.lastLogMessage()` gets the most recent, last log message recorded first, then
the next log message recorded before the last, most recent log message and so on until no more log messages
for the operation under test exists, in which case `null` is returned from `lastLogMessage()` thereafter.


[[conclusion]]
=== Conclusion

Anyway, we hope this has intrigued your interests and gets you started for now.  Ideas, contributions, or other
feedback is most welcomed.

Thank you!
