Skip to content
main
Switch branches/tags
Code

Files

Permalink
Failed to load latest commit information.
Type
Name
Latest commit message
Commit time
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Build Status

Spring Test Framework for Apache Geode & VMware Tanzu 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 Apache Geode & VMware GemFire (SDG) applications.

This project was born from Spring Data for VMware GemFire’s (@GitHub) test framework. This test framework is used in SDG’s test suite to test the proper function and behavior of Apache Geode & VMware GemFire in a Spring context.

For several years now, users have asked for a better way to test their Apache Geode & VMware GemFire based, Spring applications reliably and easily, particularly when writing Unit and Integrations tests.

Additionally, STDG was created to consolidate the testing efforts, lessons learned, and knowledge and experience of effectively testing all Spring for Apache Geode/VMware GemFire projects: Spring Boot for Apache Geode & VMware GemFire (SBDG) and Spring Session for Apache Geode & VMware GemFire (SSDG) in addition to Spring Data for Apache Geode & VMware GemFire (SDG).

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

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

In the meantime, you can review the test suite for SBDG and the test suite for SSDG to get a sense of how this project is used and works.

Code of Conduct

Please see our code of conduct

Reporting Security Vulnerabilities

Please see our Security policy.

License

Spring Test for Apache Geode and Spring Test for VMware GemFire is Open Source Software released under the Apache 2.0 license.

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 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 actual collaborators and dependencies in order to gather feedback quickly, i.e. "Is my logic correct?"

It common when writing Unit Tests to mock the collaborators/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 or the Unit Tests for the dependencies themselves), 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 collaborators/dependencies are correct and the results lead to the desired outcome.

Well, it is, or should be, no different when you are using Apache Geode or VMware 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 VMware 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
@RunWith(SpringRunner.class)
@ContextConfiguration
class ExampleUnitTestClass {

  // test case methods here

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

}

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

Here is 1 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 simple tutorial for the time being.

Mock Object Scope & Lifecycle Management

Currently, GemFire/Geode mock objects are cleaned up after an individual test class runs. Therefore, the mocked GemFire/Geode objects persist for the entire lifecycle of a single test class, or test suite, and can be reused across all the test cases of the test class.

The Spring TestContext framework emits certain "test" events during the test lifecycle as documented in the EventPublishingTestExecutionListener class Javadoc. The test events are actually contained in the org.springframework.test.context.event package.

Currently, STDG defaults the cleanup of all mocked GemFire/Geode objects to the AfterTestClassEvent type.

By way of example, this would be equivalent to:

Default STDG GemFire/Geode mock object cleanup
@RunWith(SpringRunner.class)
@ContextConfiguration
class ExampleTest {

	@ClientCacheApplication
    @EnableGemFireMockObject(destroyOnEvents = AfterTestClassEvent.class)
	static class TestConfiguration { }

}

You might want to cleanup all GemFire/Geode mock objects after each test case method in your test class using the AfterTestMethodEvent class.

In this case, you can do:

GemFire/Geode mock object cleanup after each test case
@RunWith(SpringRunner.class)
@ContextConfiguration
class ExampleTest {

	@ClientCacheApplication
    @EnableGemFireMockObject(destroyOnEvents = AfterTestMethodEvent.class)
	static class TestConfiguration { }

}

The destroyOnEvents attribute of the @EnableGemFireMockObjects annotation accepts more than one test event type, thereby allowing to perform GemFire/Geode mock object cleanup at multiple points in the test lifecycle.

For example, maybe you need to cleanup all mocked GemFire/Geode objects before each test case executes and after each test class completes:

GemFire/Geode mock object cleanup before each test case executes and after each test class completes
@RunWith(SpringRunner.class)
@ContextConfiguration
class ExampleTest {

	@ClientCacheApplication
    @EnableGemFireMockObject(destroyOnEvents = { BeforeTestExecutionEvent.class, AfterTestClassEvent.class })
	static class TestConfiguration { }

}

You now have the granularity required to control the scope and lifecycle of the GemFire/Geode mocked objects in STDG.

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, or simulate the same effects.

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

  • clear()

  • containsKey(key)

  • containsValue(value)

  • containsValueForKey(value)

  • forEach(:BiConsumer<K, V>)

  • get(key)

  • getAll()

  • getEntry(key)

  • getOrDefault(key, defaultValue)

  • invalidate(key)

  • isEmpty()

  • keySet()

  • localClear()

  • localValidate()

  • put(key, value)

  • putAll(:Map<K, V>)

  • remove(key)

  • removeAll(:Collection<K>)

  • size()

  • values()

Note
Some mock Map/Region data access operations are still being considered, such as: putIfAbsent(key, value), remove(key, value), replace(key, value), replace(key, oldValue, newValue) and replaceAll(:BiFunction<K, V>). Other mock Region data access operations will not be implemented at all (e.g. keySetOnServer() or sizeOnServer(), etc) since they necessarily involve a more complex topology. Regardless, you can still mock any Map/Region operation you like by following these instructions.
Warning
Some mock Map/Region data access operations are implemented in terms of other Map/Region operations (e.g. putAll(:Map<K, V)) is implemented in terms of put(key, value)) and are therefore compound actions that are not atomic. In other words, we did not make the atomic.

The "mock" Region will behave and function 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
@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 shields 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 in the following code.

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

Customer
@Region("Customers")
class Customer {

    @Id
    private Long id;

    // ...

}

Along with a SD Repository for Customers:

CustomerRepository
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
@RunWith(SpringRunner.class)
@ContextConfiguration
class MySpringDataRepositoryUnitTests {

    @Autowired
    private CustomerRepository customerRepository;

    @Test
    public void simpleRepositoryCrudOpsWork() {

        Customer jonDoe = new Customer(1L, "Jon Doe");

        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
@RunWith(SpringRunner.class)
@ContextConfiguration
class MySpringDataRepositoryWithMockRegionUnitTests {

    @Autowired
    private CustomerRepository customerRepository;

    @Resource(name = "Customers")
    private Region<Long, Customer> customers;

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

    @Test
    public void customerRegionOpsWorkToo() {

        Customer janeDoe = new Customer(2L, "Jane Doe");

        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);
    }
}

While you are allowed to inject a Region directly into your test class, it is better to use SDG’s GemfireTemplate, which wraps and decorates a Region’s data access operations. GemfireTemplate provides a lower-level API, closer to the Region API, than Spring Data Repositories allowing you to perform and exercise more control over advanced functions, while still shielding you from the Region API.

The test class above could be rewritten as:

Accessing the mock Region using the SDG GemfireTemplate in the SD Repository test
@RunWith(SpringRunner.class)
@ContextConfiguration
class MySpringDataRepositoryWithGemfireTemplateUnitTests {

    @Autowired
    private CustomerRepository customerRepository;

    @Autowired
    @Qualifier("customersTemplate")
    private GemfireTemplate customersTemplate;

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

    @Test
    public void customerTemplateOpsWorkToo() {

        Customer janeDoe = new Customer(2L, "Jane Doe");

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

        assertThat(customersTemplate).containsKey(janeDoe.getId());
        assertThat(customersTemplate.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 and overflow to disk, distribution, replication, eviction, expiration, querying, etc. If you find you need to test your application with these behaviors and functions, then your test would clearly be better suited as an actual Integration Test.

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
@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.

Mocking Unsupported Region Operations

As stated in the Mock Regions with Data section above, only the following Region data access operations are supported by STDG out-of-the-box (OOTB):

  • clear()

  • containsKey(key)

  • containsValue(value)

  • containsValueForKey(value)

  • forEach(:BiConsumer<K, V>)

  • get(key)

  • getAll()

  • getEntry(key)

  • getOrDefault(key, defaultValue)

  • invalidate(key)

  • isEmpty()

  • keySet()

  • localClear()

  • localValidate()

  • put(key, value)

  • putAll(:Map<K, V>)

  • remove(key)

  • removeAll(:Collection<K>)

  • size()

  • values()

How then do you mock other Region operations (e.g. putIfAbsent(key, value)) provided by the Region API that is not supported by STDG OOTB?

Fortunately, you can rely on the fact that the Region object returned when mocking with @EnableGemFireMockObjects inside your Unit Tests is a "mock" object, specifically mocked by Mockito. Therefore, you are able to mock any other Region data access operations that might be required by your application given a reference to the "mock" Region object.

For example, suppose you also want to mock the putIfAbsent(key, value) Map operation on Region, then you can do:

Mocking Region.putIfAbsent(key, value)
@RunWith(SpringRunner.class)
@ContextConfiguration
class ExampleUnitTest {

	@Autowired
    @Qualifer("exampleTemplate")
	GemfireTemplate exampleTemplate;

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

	@Before
	public void setup() {

		doAnswer(invocation -> {

			Object key = invocation.getArgugment(0);
			Object value = invocation.getArgument(1);
			Object existingValue;

			synchronized (this.example) {

				existingValue = this.example.get(key);

				if (existingValue == null) {
					this.example.put(key, value);
				}
			}

			return existingValue;

		}).when(this.example).putIfAbsent(any(), any());
	}

	@Test
	public void putIfAbsentWorks() {

		assertThat(this.exampleTemplate.putIfAbsent(1, "test")).isNull();
		assertThat(this.exampleTemplate.putIfAbsent(1, "mock")).isEqualTo("test");
		assertThat(this.exampleTemplate.get(1)).isEqualTo("test");
	}

	@ClientCacheApplication
    @EnableGemFireMockObjects
	static class TestConfiguration {

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

            ClientRegionFactoryBean mockRegion = new ClientRegionFactoryBean();

            mockRegion.setCache(gemfireCache);

            return mockRegion;
        }

        @Bean
        GemfireTemplate exampleTemplate(GemFireCache gemfireCache) {
        	return new GemfireTemplate(gemifreCache.getRegion("/Example"));
        }
	}
}

While the putIfAbsent(key, value) operation above was mocked (implemented) in terms of the existing, mocked get(key) and put(key, value) Region operations, you could very well have implemented/mocked putIfAbsent(key, value) however you wanted. The Region object is a "mock" object after all.

Not only can you mock unsupported Region methods, you can also redefine the mocked behavior of a STDG supported and mocked Region method, like get(key) or put(key, value) as well.

This capability applies to any GemFire/Geode mocked object. The choice is up to you what a GemFire/Geode mock object does or does not do.

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
@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 example of a concrete client/server Integration Test extending STDG’s ForkingClientServerIntegrationTestsSupprt class.

Notice, too, that I am using SDG’s 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 Issue #9 for more details. This feature would be loosely based on, and similar to, Spring Boot Testing with Test Slices.

Cleaning up after GemFire/Geode during Integration Tests

When writing Integration Tests using "live" GemFire/Geode objects (e.g. Regions), those object can leave artifacts behind after a test run completes.

This can potentially cause conflicts between Integration Test Cases that use features like persistence having similarly named Regions particularly if you are not careful to differentiate the working directory between your tests. This is also problematic, especially when switching between versions of GemFire/Geode, used by your application, during testing. Perhaps you are in the middle of testing a (rolling) upgrade.

At any rate, STDG has you covered. If you would like to make sure that artifacts are properly cleaned up after a test run, then you can annotate your test class with STDG’s @EnableGemFireResourceCollector annotation, like so:

Using `@EnableGemFireResourceCollector
@RunWith(SpringRunner.class)
@ContextConfiguration
class ExampleIntegrationTest {

	@CacheServerApplication
    @EnableLocator
    @EnableManager
    @EnableGemFireResourceCollector
    static class TestGeodeConfiguration { }

}

Like the @EnableGemFireMockObjects annotation, you can control which Spring TestContext test event will trigger a GemFire/Geode resource (garbage) collection process using the collectOnEvents attribute.

Also, you can attempt to clean any GemFire/Geode DiskStore files (created by persistence, overflow or PDX) by setting the @EnableGemFireResourceCollector annotation, tryCleanDiskStoreFiles attribute to true.

The following list of GemFire/Geode files with extensions or names are cleaned up by STDG’s @EnableGemFireResourceCollector functionality:

Table 1. GemFire/Geode File Extensions
File Extension Description

.dat

Locator view file; e.g. locator10334view.dat

.gfs

Statistics archive file

.crf

Oplog file containing create, update, invalidate operations

.drf

Oplog file containing delete operations

.if

DiskStore metadata file

.krf

Oplog file for key and crf offset information

.lk

DiskStore access control file

.log

Log files created by GemFire/Geode process (Locators, Servers, Manager, etc)

.pid

File containing the OS process ID of the GemFire/Geode process (Locator, Server, etc)

.properties

GemFire/Geode properties configuration file (e.g. gemfire.properties)

.xml

GemFire/Geode XML configuration file (e.g. cache.xml)

Table 2. GemFire/Geode Filenames
Filename Description

backup

filename prefix

cache

filename prefix

configdiskdir

Cluster Configuration Service directory name

default

filename prefix

drlk_if

filename prefix

gfsecurity

filename prefix

gemfire

directory/file name

geode

directory/file name

locator

directory/file prefix name

overflow

filename prefix

The names of file extensions and files/directories are treated by STDG as case insensitive when matching.

For a complete list of artifacts created by GemFire/Geode processes, follow the link.

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
<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
<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
@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
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 = new NetworkEvent();

    this.service.processDenialOfServiceAttack(event);

    assertThat(testAppender.lastLogMessage())
      .isEqualTo("A DDoS attack occurred 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 = new LoginRequest();

      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

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

Thank you!

About

A Spring-based Test Framework supporting Unit and Integration testing for Spring Boot applications using Spring Data with either Apache Geode or VMware Tanzu GemFire

Resources

License

Packages

No packages published

Languages