How to test Spring's declarative caching support on Spring Data repositories? - spring

I have developed a Spring Data repository, MemberRepository interface, that extends org.springframework.data.jpa.repository.JpaRepository. MemberRepository has a method:
#Cacheable(CacheConfiguration.DATABASE_CACHE_NAME)
Member findByEmail(String email);
The result is cached by Spring cache abstraction (backed by a ConcurrentMapCache).
The issue I have is that I want to write an integration test (against hsqldb) that asserts that the result is retrieved from db the first time and from cache the second time.
I initially thought of mocking the jpa infrastructure (entity manager, etc.) and somehow assert that the entity manager is not called the second time but it seems too hard/cumbersome (see https://stackoverflow.com/a/23442457/536299).
Can someone then please provide advice as to how to test the caching behavior of a Spring Data Repository method annotated with #Cacheable?

If you want to test a technical aspect like caching, don't use a database at all. It's important to understand what you'd like to test here. You want to make sure the method invocation is avoided for the invocation with the very same arguments. The repository fronting a database is a completely orthogonal aspect to this topic.
Here's what I'd recommend:
Set up an integration test that configures declarative caching (or imports the necessary bit's and pieces from your production configuration.
Configure a mock instance of your repository.
Write a test case to set up the expected behavior of the mock, invoke the methods and verify the output accordingly.
Sample
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration
public class CachingIntegrationTest {
// Your repository interface
interface MyRepo extends Repository<Object, Long> {
#Cacheable("sample")
Object findByEmail(String email);
}
#Configuration
#EnableCaching
static class Config {
// Simulating your caching configuration
#Bean
CacheManager cacheManager() {
return new ConcurrentMapCacheManager("sample");
}
// A repository mock instead of the real proxy
#Bean
MyRepo myRepo() {
return Mockito.mock(MyRepo.class);
}
}
#Autowired CacheManager manager;
#Autowired MyRepo repo;
#Test
public void methodInvocationShouldBeCached() {
Object first = new Object();
Object second = new Object();
// Set up the mock to return *different* objects for the first and second call
Mockito.when(repo.findByEmail(Mockito.any(String.class))).thenReturn(first, second);
// First invocation returns object returned by the method
Object result = repo.findByEmail("foo");
assertThat(result, is(first));
// Second invocation should return cached value, *not* second (as set up above)
result = repo.findByEmail("foo");
assertThat(result, is(first));
// Verify repository method was invoked once
Mockito.verify(repo, Mockito.times(1)).findByEmail("foo");
assertThat(manager.getCache("sample").get("foo"), is(notNullValue()));
// Third invocation with different key is triggers the second invocation of the repo method
result = repo.findByEmail("bar");
assertThat(result, is(second));
}
}
As you can see, we do a bit of over-testing here:
The most relevant check, I think is that the second call returns the first object. That's what the caching is all about. The first two calls with the same key return the same object, whereas the third call with a different key results in the second actual invocation on the repository.
We strengthen the test case by checking that the cache actually has a value for the first key. One could even extend that to check for the actual value. On the other hand, I also think it's fine to avoid doing that as you tend to test more of the internals of the mechanism rather than the application level behavior.
Key take-aways
You don't need any infrastructure to be in place to test container behavior.
Setting a test case up is easy and straight forward.
Well-designed components let you write simple test cases and require less integration leg work for testing.

I tried testing the cache behavior in my app using Oliver's example. In my case my cache is set at the service layer and I want to verify that my repo is being called the right number of times. I'm using spock mocks instead of mockito. I spent some time trying to figure out why my tests are failing, until I realized that tests running first are populating the cache and effecting the other tests. After clearing the cache for every test they started behaving as expected.
Here's what I ended up with:
#ContextConfiguration
class FooBarServiceCacheTest extends Specification {
#TestConfiguration
#EnableCaching
static class Config {
def mockFactory = new DetachedMockFactory()
def fooBarRepository = mockFactory.Mock(FooBarRepository)
#Bean
CacheManager cacheManager() {
new ConcurrentMapCacheManager(FOOBARS)
}
#Bean
FooBarRepository fooBarRepository() {
fooBarRepository
}
#Bean
FooBarService getFooBarService() {
new FooBarService(fooBarRepository)
}
}
#Autowired
#Subject
FooBarService fooBarService
#Autowired
FooBarRepository fooBarRepository
#Autowired
CacheManager cacheManager
def "setup"(){
// we want to start each test with an new cache
cacheManager.getCache(FOOBARS).clear()
}
def "should return cached foobars "() {
given:
final foobars = [new FooBar(), new FooBar()]
when:
fooBarService.getFooBars()
fooBarService.getFooBars()
final fooBars = fooBarService.getFooBars()
then:
1 * fooBarRepository.findAll() >> foobars
}
def "should return new foobars after clearing cache"() {
given:
final foobars = [new FooBar(), new FooBar()]
when:
fooBarService.getFooBars()
fooBarService.clearCache()
final fooBars = fooBarService.getFooBars()
then:
2 * fooBarRepository.findAll() >> foobars
}
}

Related

Spring Boot / Junit5+Mockito: Mock's mockitoInterceptor gets replaced during test?

I have two service classes (there are more, of course, but those two are relevant here), which are in use during an integration test.
For test, I set up a mock (ConfigurationService) and stub two methods:
#ExtendWith(SpringExtension.class)
#ActiveProfiles("test")
#SpringBootTest
#TestPropertySource(properties =
"spring.main.allow-bean-definition-overriding=true")
public class VehicleAuditExecutionServiceIT {
#MockBean
private ConfigurationService configurationServiceMock;
#Autowired
private VehicleAuditExecutionService vehicleAuditExecutionService;
#Test
void testExecuteVehicleAudits() throws IOException {
// quite some DB operations for the test setup here
AuditDurationConfigDTO auditDurationConfigDTO = new AuditDurationConfigDTO();
auditDurationConfigDTO.setMaxDuration(deMaxDuration);
Map<String, ScheduledAuditConfigDTO> map = new HashMap<>();
map.put(country, ScheduledAuditConfigDTO.builder()
.groupCalculationEnabled(false)
.build());
when(configurationServiceMock.getAuditDurationConfig(country)).thenReturn(auditDurationConfigDTO);
when(configurationServiceMock.getScheduledAuditConfigurationForCountry(country)).thenReturn(ScheduledAuditConfigDTO.builder()
.groupCalculationEnabled(false)
.sameAuditDurationAllDealersSameGroupId(false)
.build());
vehicleAuditExecutionService.executeVehicleAudits(startDate, country);
verify(publishAuditInterfaceMock).pushExecutions(country, dealerDe1ExportAuditDtoList, auditCategory);
}
#ComponentScan(basePackages = "com.application")
#SpringBootApplication
#PropertySource("classpath:application-test.yml")
static class TestConfiguration {}
}
After the setup, the stubbings are available:
During the test's execution, vehicleAuditExecutionService.executeVehicleAudits(startDate, country) calls the AuditPreparationService, which in turn uses the configurationServiceMock (using #Autowired constructor injection). As expected, the calls gets matched and result set up is returned.
Later, the execution returns to vehicleAuditExecutionService.executeVehicleAudits(startDate, country), where it calls the configurationServiceMock (#Autowired constructor injection, as well) again. But here, the mock's configuration has been changed: the mock's attribute mockitoInterceptor gets replaced by some other instance.
Result: the stubbing is gone and the call returns null - leading to a NPE.
The screenshots were taken using org.springframework.boot:spring-boot-starter-parent:2.7.6, but I've tried that with multiple Spring Boot versions:
2.7.0
2.6.14
2.5.14
2.4.13
2.3.12.RELEASE
Each version has this issue - but I've never seen it in any other test. So I guess, there's something wrong with my test setup - but I cannot spot it.
Any idea, why this is happening?
Thanks a lot - please do not hesitate to ask for any further information, if needed for analysis.
kniffte

#Cacheable testing over method

I have a #Cacheable method inside a class.
I try to create that cache after a first call to that method, then, the second call should't go inside the method getCacheLeads.
#Service
public class LeadService {
#Autowired
private LeadRepository leadRepository;
#Autowired
public LeadService(LeadRepository leadRepository) {
this.leadRepository = leadRepository;
}
public void calculateLead(Lead leadBean) {
Lead lead = this.getCacheLeads(leadBean);
}
#Cacheable(cacheNames="leads", key="#leadBean.leadId")
public Lead getCacheLeads(Lead leadBean){
Lead result = leadRepository.findByLeadId(leadBean.getLeadId());
***logic to transform de Lead object***
return result;
}
}
But during testing that cache is never used, calling it twice with same parameter (serviceIsCalled) to ensure it is called twice to check it.
#ExtendWith(SpringExtension.class)
public class LeadServiceTest {
private LeadService leadService;
#Mock
private LeadRepository leadRepository;
#Autowired
CacheManager cacheManager;
#BeforeEach
public void setUp(){
leadService = new LeadService(leadRepository);
}
#Configuration
#EnableCaching
static class Config {
#Bean
CacheManager cacheManager() {
return new ConcurrentMapCacheManager("leads");
}
}
#Test
public void testLead(){
givenData();
serviceIsCalled();
serviceIsCalled();
checkDataArray();
}
private void givenData() {
Lead lead = new Lead();
lead.setLeadId("DC635EA19A39EA128764BB99052E5D1A9A");
Mockito.when(leadRepository.findByLeadId(any()))
.thenReturn(lead);
}
private void serviceIsCalled(){
Lead lead = new Lead();
lead.setLeadId("DC635EA19A39EA128764BB99052E5D1A9A");
leadService.calculateLead(lead);
}
private void checkDataArray(){
verify(leadRepository, times(1)).findByLeadId(anyString());
}
}
Why is it called 2 times?
You have a lot of things going on here, and someone looking at this and answering your question would definitely have to read between the lines.
First, your Spring configuration is not even correct. You are declaring the names of all the caches used by your Spring application (and tests) "statically" with the use of the ConcurrentMapCacheManager constructor accepting an array of cache names as the argument.
NOTE: Caches identified explicitly by name, and only these caches, are available at runtime.
#Bean
CacheManager cacheManager() {
return new ConcurrentMapCacheManager("LEAD_DATA");
}
In this case, your 1 and only cache is called "LEAD_DATA".
NOTE: Only the no arg constructor in `ConcurrentMapCacheManager allows dynamically created caches by name at runtime.
But then, in your #Service LeadService class, #Cacheable getCacheLeads(:Lead) method, you declare the cache to use as "leads".
#Service
public class LeadService {
#Cacheable(cacheNames="leads", key="#leadBean.leadId")
public Lead getCacheLeads(Lead leadBean){
// ...
}
}
This miss configuration will actually lead to an Exception at runtime similar to the following:
java.lang.IllegalArgumentException: Cannot find cache named 'leads' for Builder[public io.stackoverflow.questions.spring.cache.StaticCacheNamesIntegrationTests$Lead io.stackoverflow.questions.spring.cache.StaticCacheNamesIntegrationTests$LeadService.load(io.stackoverflow.questions.spring.cache.StaticCacheNamesIntegrationTests$Lead)] caches=[leads] | key='#lead.id' | keyGenerator='' | cacheManager='' | cacheResolver='' | condition='' | unless='' | sync='false'
at org.springframework.cache.interceptor.AbstractCacheResolver.resolveCaches(AbstractCacheResolver.java:92)
at org.springframework.cache.interceptor.CacheAspectSupport.getCaches(CacheAspectSupport.java:252)
at org.springframework.cache.interceptor.CacheAspectSupport$CacheOperationContext.<init>(CacheAspectSupport.java:724)
at org.springframework.cache.interceptor.CacheAspectSupport.getOperationContext(CacheAspectSupport.java:265)
at org.springframework.cache.interceptor.CacheAspectSupport$CacheOperationContexts.<init>(CacheAspectSupport.java:615)
at org.springframework.cache.interceptor.CacheAspectSupport.execute(CacheAspectSupport.java:345)
at org.springframework.cache.interceptor.CacheInterceptor.invoke(CacheInterceptor.java:64)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:753)
at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:698)
at io.stackoverflow.questions.spring.cache.StaticCacheNamesIntegrationTests$LeadService$$EnhancerBySpringCGLIB$$86664246.load(<generated>)
...
..
.
Additionally, I don't see anything "outside" of the LeadsService bean calling the #Cacheable, getCacheLeads(..) method. Inside your test, you are calling:
leadService.calculateLead(lead);
As follows:
private void serviceIsCalled(){
Lead lead = new Lead();
lead.setLeadId("DC635EA19A39EA128764BB99052E5D1A9A");
leadService.calculateLead(lead);
}
If the calculateLead(:Lead) LeadService method is calling the #Cacheable, getCacheLeads(:Lead) LeadService method (internally), then that is not going to cause the caching functionality to kick in since you are already "behind" the AOP Proxy setup by Spring to "enable" caching behavior for your LeadService bean.
See the Spring Framework AOP documentation on this matter.
NOTE: Spring's Cache Abstraction, like the Spring's Transaction Management, is built on the Spring AOP infrastructure, as are many other things in Spring.
In your case this means:
Test -> <PROXY> -> LeadService.calculateLead(:Lead) -> LeadService.getCacheLeads(:Lead)
However, between LeadSevice.calculateLead(:Lead) and LeadService.getCacheLeads(:Lead), NO PROXY is involved, therefore Spring's caching behavior will not be applied.
Only...
Test (or some other bean) -> <PROXY> -> LeadService.getCacheLeads(:Lead)
Will result in the AOP Proxy decorated with the Caching Interceptors being invoked and the caching behavior applied.
You can see that your use case will work correctly when configured and used correctly as demonstrated in my example test class, modeled after your domain.
Look for the comments that explain why your configuration will fail in your case.

Spring H2 Test DB does not reset before each test

EDIT: As C. Weber suggested in the comments, the solution is to add #Transactional to the test class.
I have some tests that use an H2 in-memory DB. I need to reset the DB before each test. Although my SQL scripts are run each a test is executed, the DB is not properly reset, resulting in a missing needed entry after a delete test.
Test class:
#RunWith(SpringRunner.class)
#SpringBootTest
#AutoConfigureTestDatabase(replace=Replace.ANY, connection=EmbeddedDatabaseConnection.H2)
public class RepositoryTests {
#Autowired
private Repository repository;
#Autowired
private DataSource dataSource;
#Before
public void populateDb() {
Resource initSchema = new ClassPathResource("database/schema.sql");
Resource initData = new ClassPathResource("database/data.sql");
DatabasePopulator dbPopulator = new ResourceDatabasePopulator(initSchema, initData);
DatabasePopulatorUtils.execute(dbPopulator, dataSource);
}
#Test
public void testMethod1() {
// ...
repository.delete("testdata");
}
#Test
public void testMethod2() {
// ...
Object test = repository.get("testdata");
// is null but should be an instance
}
}
schema.sql drops all tables before recreating them. data.sql inserts all needed test data into the DB.
Running the testMethod2 alone succeeds. However, running all tests makes the test fail with a NullPointerException.
I have successfully tried to use #DirtiesContext, however this is not an option because I can't afford to have a 20 second startup for each 0.1 second test.
Is there another solution?
The Spring Test Framework provides a mechanism for the behaviour you want for your tests. Simply annotate your Test class with #Transactional to get the default rollback behaviour for each test method.
There are ways to configure the transactional behaviour of tests and also some pitfalls (like using RestTemplate inside test method), which you can read more about in the corresponding chapter of the Spring manual.
Spring Test Framework

Mocking a constructor of #Autowired service(system under test)

I have to mock jerseyclient which is being created in Constructor of subjected service. Subjected service is System under test injected via Spring's #Autowired.
In constructor of the service client=client.create() method is written. We can't change this code(Although this is a code smell). I want to mock the jersey client but it is in constructor of the service. I am not able to mock this
sooo... long story short.. admitting you use mockito, in your src for test you should have an applicationcontext for your test.. usually we define one programmatically so, something along those lines..
import the .xml file you use for test purpose (in my case i imported the one for the mailserver, for the connection and for the authentication) instead of the one i use for the "local" environmnet. After then define a method to setup each and every of your service.
You might need to add a mock for your template resolver as well, but ultimately this all depends on your stack...
So based on your approach the final thing might be a bit different, but ultimately you're gonna do something along the lines of what i outline below:
#Configuration
#ImportResource(value = {
"classpath:applicationContext-jdbc-test.xml",
"classpath:applicationContext-ldap-test.xml",
"classpath:applicationContext-mail-test.xml"})
public class ApplicationTestContext {
#Bean
public ObjectMapperWrapper objectMapperWrapper() {
return Mockito.mock(ObjectMapperWrapper.class);
}
#Bean
public YourService yourService() {
return new YourServiceImpl();
}
}

Unit testing with Mockito

I am writing unit tests for service layer in my spring application.
Here is my service class
#Service
public class StubRequestService implements RequestService {
#Autowired
private RequestDao requestDao;
#Transactional(propagation = Propagation.REQUIRED, readOnly = true)
#Override
public Request getRequest(Long RequestId) {
Request dataRequest = requestDao.find(requestId);
return dataRequest;
}
}
Here is my test class
#RunWith(MockitoJUnitRunner.class)
#ContextConfiguration(locations = { "/META-INF/spring/applicationContext.xml" })
public class StubRequestServiceTest {
#Mock
public RequestDao requestDao;
StubRequestService stubRequestService; // How can we Autowire this ?
#org.junit.Before
public void init() {
stubRequestService = new StubRequestService(); // to avoid this
stubRequestService.setRequestDao(dataRequestDao);
// Is it necessary to explicitly set all autowired elements ?
// If I comment/remove above setter then I get nullPointerException
}
#Test
public void testGetRequest() {
Request request = new Request();
request.setPatientCnt("3");
when(requestDao.find(anyLong())).thenReturn(request);
assertEquals(stubRequestService.getRequest(1234L).getPatientCnt(),3);
}
}
Its working fine but I have few questions
How can we Autowire service class in test ? I am using constructor in init() method to create service object.
Do we have to set all Autowire element for service class ? For ex StubRequestService have autowired RequestDao which I need to set explicitly before calling test method otherwise it giveds nullPointerException as requestDao is null in StubRequestService.getRequest method.
Which are the good practices to follow while unit testing Spring service layer ? (If I am doing anything wrong).
Your test is fine. It doesn't even have to have the #ContextConfiguration annotation.
The whole point of dependency injection frameworks like Spring is to be able to unit test services by simply instantiating them, setting mock dependencies, and then call their methods.
You're doing it correctly. You don't need to have a Spring context for such unit tests. That's why they're called unit tests: they test it in isolation of all their actual dependencies, Spring included.
Side note: assuming you're using JUnit, the arguments of the assertXxx method should be swapped. The expected value comes before the actual value. It becomes important when the assertion fails and you have a message like "expecting 6 but was 3" rather than "expecting 3 but was 6".
If you really feel that it will make your tests easier to understand - you can initialize a spring context and fetch all of the objects from there. However, usually it will require creating a separate spring configuration XML file specifically for tests therefore I would not recommend it.
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("testApplicationContext.xml");
stubRequestService = (RequestService)applicationContext.getBean("myRequestServiceBean");
(and 3) Basically, I prefer testing each component of my application in total isolation from eachother and that's why I do not recommend what I described in [1].
What that means, is you take a separate logical slice of your application and test only it, while fully mocking up everything it tries to access.
Let's say you have three classes:
//Fetches stuff from some webservice and converts to your app domain POJOs
class DataAccessLayer {
public void setWebservice(Webservice ws) {...};
public MyObject getMyObject() {...};
}
//Formats the domain POJOs and sends them to some kind of outputstream or stuff.
class ViewLayer {
public void setOutputStream(OutputStream os) {...};
public void viewMyObject(MyObject mo) {...};
}
//Main entry point of our MyObject fetch-process-display workflow
class Controller {
public void setDataAccessLayer(DataAccessLayer dal) {...};
public void setViewLayer(ViewLayer vl) {...};
public void showMyObject() {
MyObject mo = dal.getMyObject();
...some processing here maybe...
vl.viewMyObject(mo);
}
}
Now, what tests can we write here?
Test if DataAccessLayer properly converts the object from mocked up WS to our domain object.
Test if ViewLayer properly formats the object given to him and writes it to mocked up output stream.
Test if Controller takes an object from mocked up DataAccessLayer processes it properly and sends it to mocked up ViewLayer.
Or You can use springockito
https://bitbucket.org/kubek2k/springockito/wiki/Home, it will make your tests cleaner

Resources