jUnit @Rule and Spring Caches

If you have worked a while with jUnit you may have seen jUnit Rules. Simply put, a field in a test class annotated with @Rule is a class that lets you execute some code that runs before and/or after your unit test, similar to the @Before and @After annotations. Consequently, they solve the same problem, However, the code exercised by the @Before and @After annotations is usually tied to the specific test class whereas the logic implemented in a @Rule tends to be of more generic nature. For example, jUnit provides the TemporaryFolder that creates a temporary folder that can be used during a test and deletes it and any files and folders recursively that were created after the test. Another example is the ExpectedException that is useful when verifying exceptions.

Spring Integration Tests

As stated in the Testing chapter in the Spring Reference Docs:

Once the TestContext framework loads an ApplicationContext (or WebApplicationContext) for a test, that context will be cached and reused for all subsequent tests that declare the same unique context configuration within the same test suite.

Among other things, this means that if your application contains a cache managed by Spring (for example if you use the @Cachable annotation), there is a possibility that there are residual objects from previous tests in the cache when a new test begins. As a consequence, you may experience that the tests will behave different during different executions, for example if a single test is executed compared to when it is executed as part of the entire test suite. To prevent this you can either create a specific @Before method that clears the cache that can be copied between test files, create a common AbstractTest class with the eviction logic that can be inherited to all relevant test classes, create a separate TestSupport class that the test classes can delegate to, or create a custom test rule.

CacheInvalidationRule

Alternatively, if you are not yet able to use Java 8 in production:

The ExternalResource is an abstract class provided by jUnit which implements the TestResource interface. Moreover, it provides an before() method and an after() method that can be overridden. Here, we chose to clear all caches provided by the cache manager.

Usage

In order for this to work, the TestApplicationContext can either @ComponentScan the package in which the CacheInvalidationRule is located (since it is annotated with the @Service annotation) or declare it as a bean, e.g.

In either way, the CacheInvalidationRule is available in the TestApplicationContext and can thus be autowired in the ExampleTest. Do not forget the @Rule annotation required by jUnit.

Changing Cache Implementations

Depending on your application requirements, you may choose different cache implementations. If you use simple caches like EhCache or Guava you can probably use the cache configuration like it is. On the other hand, it may not be desirable to have tests that depend on external infrastructure such as a GemFire cluster. One way to avoid this is to add an in memory cache backed by Java’s standard ConcurrentHashMap to your test configuration:

In the example above, only a single cache was added to the cache manager, but you can easily imagine how more caches can be added. The inclusion of the @Primary annotation brings about that it is this cache manager that shall be used by Spring during the test and not the default one that may be included by some other (production) application context.

Caveat

It is only possible to use the cache invalidation rule if you have access to the cache manager that lives in your application context from the test class. For Spring integration tests (i.e. those that use the SpringJUnit4ClassRunner) this is not a problem. However, if you create a webapp that is tested by firing up an embedded Tomcat or embedded Jetty, which starts Spring internally, then the application context and its cache manager is not available to the test class, at least not out of the box. In contrast, Spring Boot can use its embedded servlet container and still have access to its application context, and thus autowire any of its beans, see my previous blog post Integration Testing a Spring Boot Application.

References

Mattias Severson

Mattias is a senior software engineer specialized in backend architecture and development with experience of cloud based applications and scalable solutions. He is a clean code proponent who appreciates Agile methodologies and pragmatic Test Driven Development. Mattias has experience from many different environments, including everything between big international projects that last for years and solo, single day jobs. He is open-minded and curious about new technologies. Mattias believes in continuous improvement on a personal level as well as in the projects that he is working on. Additionally, Mattias is a frequent speaker at user groups, companies and conferences.

This Post Has One Comment

  1. That addssrees several of my concerns actually.

Leave a Reply

Close Menu