I created a new integration test Class to test one of my service implementation. I use @MockBean to mock other beans. When I launch only this test Class everything works fine. However when I run all tests with JUnit or Maven verify the tests running after my test have errors they do not have when the @MockBean is not present
The exception thrown in the other tests seems linked to the cache
Exception
java.lang.IllegalStateException
at com.github.benmanes.caffeine.jcache.CacheProxy.requireNotClosed(CacheProxy.java:1054)
at com.github.benmanes.caffeine.jcache.CacheProxy.remove(CacheProxy.java:465)
at org.springframework.cache.jcache.JCacheCache.evict(JCacheCache.java:106)
at com.mycompany.myapp.service.UserService.clearUserCaches(UserService.java:292)
at com.mycompany.myapp.service.UserService.lambda$completePasswordReset$2(UserService.java:73)
at java.base/java.util.Optional.map(Optional.java:265)
at com.mycompany.myapp.service.UserService.completePasswordReset(UserService.java:69)
at com.mycompany.myapp.service.UserService$$FastClassBySpringCGLIB$$2169c3ce.invoke()
at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:218)
at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.invokeJoinpoint(CglibAopProxy.java:750)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:163)
at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:295)
at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:98)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:689)
at com.mycompany.myapp.service.UserService$$EnhancerBySpringCGLIB$$9f6eeabd.completePasswordReset()
at com.mycompany.myapp.service.UserServiceIT.assertThatUserCanResetPassword(UserServiceIT.java:145)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.base/java.lang.reflect.Method.invoke(Method.java:566)
at org.junit.platform.commons.util.ReflectionUtils.invokeMethod(ReflectionUtils.java:532)
at org.junit.jupiter.engine.execution.ExecutableInvoker.invoke(ExecutableInvoker.java:115)
at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.lambda$invokeTestMethod$6(TestMethodTestDescriptor.java:171)
at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:72)
at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.invokeTestMethod(TestMethodTestDescriptor.java:167)
at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.execute(TestMethodTestDescriptor.java:114)
at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.execute(TestMethodTestDescriptor.java:59)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$4(NodeTestTask.java:108)
at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:72)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:98)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:74)
at java.base/java.util.ArrayList.forEach(ArrayList.java:1540)
at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.invokeAll(SameThreadHierarchicalTestExecutorService.java:38)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$4(NodeTestTask.java:112)
at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:72)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:98)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:74)
at java.base/java.util.ArrayList.forEach(ArrayList.java:1540)
at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.invokeAll(SameThreadHierarchicalTestExecutorService.java:38)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$4(NodeTestTask.java:112)
at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:72)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:98)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:74)
at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.submit(SameThreadHierarchicalTestExecutorService.java:32)
at org.junit.platform.engine.support.hierarchical.HierarchicalTestExecutor.execute(HierarchicalTestExecutor.java:57)
at org.junit.platform.engine.support.hierarchical.HierarchicalTestEngine.execute(HierarchicalTestEngine.java:51)
at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:220)
at org.junit.platform.launcher.core.DefaultLauncher.lambda$execute$6(DefaultLauncher.java:188)
at org.junit.platform.launcher.core.DefaultLauncher.withInterceptedStreams(DefaultLauncher.java:202)
at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:181)
at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:128)
at com.intellij.junit5.JUnit5IdeaTestRunner.startRunnerWithArgs(JUnit5IdeaTestRunner.java:69)
at com.intellij.rt.execution.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:47)
at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:242)
at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:70)
Using @MockBean is really powerful to test spring boot applications it simplifies the bean and mock creations in SpringBootTest
To reproduce the error I created a repo you can find here
The test I created is here
To reproduce the error just clone the repo and run tests with Maven or JUnit
I found this old issue https://github.com/jhipster/generator-jhipster/issues/5354
It seems to be the same kind of stuff. Something with Spring Context and Cache configuration when running tests.
When running test with @MockBean SpringBoot recreates a new Context but it seems to not recreate proper cache
I have no idea how to fix the bug but if someone can explain me where does it come I can help to fix it
I am using 6.4.1 version with caffeine cache
.yo-rc.json file
{
"generator-jhipster": {
"promptValues": {
"packageName": "com.mycompany.myapp",
"nativeLanguage": "en"
},
"jhipsterVersion": "6.4.1",
"applicationType": "monolith",
"baseName": "tester",
"packageName": "com.mycompany.myapp",
"packageFolder": "com/mycompany/myapp",
"serverPort": "8080",
"authenticationType": "jwt",
"cacheProvider": "caffeine",
"enableHibernateCache": true,
"websocket": false,
"databaseType": "sql",
"devDatabaseType": "h2Disk",
"prodDatabaseType": "postgresql",
"searchEngine": false,
"messageBroker": false,
"serviceDiscoveryType": false,
"buildTool": "maven",
"enableSwaggerCodegen": false,
"jwtSecretKey": "xxxxxxxx",
"embeddableLaunchScript": false,
"useSass": true,
"clientPackageManager": "npm",
"clientFramework": "angularX",
"clientTheme": "none",
"clientThemeVariant": "",
"testFrameworks": [],
"jhiPrefix": "jhi",
"entitySuffix": "",
"dtoSuffix": "DTO",
"otherModules": [],
"enableTranslation": true,
"nativeLanguage": "en",
"languages": ["en"],
"blueprints": []
}
}
entityName.json files generated in the .jhipster directoryYou can get all the informations in the repo
Using windows 10
I tried adding "spring.cache.type=none" to test application.yml but it did not work.
Additionally, I added a condition on CacheConfiguration:
~java
@ConditionalOnExpression("'${spring.cache.type}'!='none'")
public class CacheConfiguration {
~
but it failed because UserService depends on CacheManager for clearing caches.
I think that UserService should be able to work without caching.
So I created a NoCacheConfiguration class in test to provide a NoOpCacheManager which is probably what Spring Boot autoconfiguration does for "spring.cache.type=none" and all tests passed except UserResourceIT#getUser which purpose is to test caching.
~~~java
@Configuration
@EnableCaching
@ConditionalOnExpression("'${spring.cache.type}'=='none'")
public class NoCacheConfiguration {
@Bean
public CacheManager cacheManager() {
return new NoOpCacheManager();
}
}
~~~
Some ideas also from: https://stackoverflow.com/questions/57201103/spring-boot-test-tries-to-initialize-cache2k-for-the-2nd-time-and-fails
Here is the full code as a PR to your test repo: https://github.com/Ckram/jhi-tester/pull/1
I did not do extensive testing.
Thanks @gmarziou I understand the workaround.
Do you think we could use a specific profile with @ActiveProfiles for class tests using cache to enable real cache and test it" and another profile NoCache for test with @MockBeans ?
Yes, personally I would prefer disabling cache for tests by default to avoid side effects if cache is not cleared between tests (though I did not check it is the case) and have a cache profile for tests that require it and that could be annotated with @DirtiesContext.
But I think that JHipster should honor spring.cache.type=none such as autoconfiguration does.
Maybe @henri-tremblay you have an opinion to share?
Does it happen with other cache implementations? It seems the cache is closed. Which probably means the app context was closed. But then, the same cache is reused. Most probably because it wasn't removed from JCache. So from here, it might be a Cafeine bug, a Spring Cache bug (I doubt it though) or a JHipster bug (maybe). I would check why the cache is closed and then retrieved.
I can try with other cache implementations and let you know what happens
I have the same errors with ehcache
@gmarziou @Ckram : so is it a real JHipster bug ? Or a specific question about additional code ?
Because, if it concerns additional code, it should go to stackoverflow and not here.
It feels like a bug. From my point of view a new ApplicationContext should use a new cache manager. Or the previous one should be cleared. I don't have the time to check right now but can in a few days.
I agree, I'm currently travelling and can't help for next 10 days.
I did not test but I wonder if this could happen also in prod upon an application context refresh, it looks also similar to issues with elasticsearch.
I have dug in it a little bit. The problem seems to be that
@MockBean taint the Spring context in some wayCacheConfiguration to be run againCacheManager is retrieved, which is expectedCacheConfiguration destroys the cache and create a new one. All wrapped under a new JCacheCacheManagerJCacheCacheManager are staying a dependency to other beans like UserServiceJCacheCacheManager, it finds old JCache linked to old Cache that are now destroyed. Thus the problem.A solution is to do that:
private void createCache(javax.cache.CacheManager cm, String cacheName) {
javax.cache.Cache<Object, Object> cache = cm.getCache(cacheName);
if (cache != null) {
cache.clear();
} else {
cm.createCache(cacheName, jcacheConfiguration);
}
}
Since we are in the same context, the configuration is the same so clearing the cache should be fine.
However, it looks like a Spring bug. But I'm not sure at which level. Maybe the JCacheCacheConfiguration should not create a new JCacheCacheManager.
My 2 cents
We dealt with the same error in a JHipster 6.6+ application since we added @AutoConfigureMockMvc annotation in our integration tests.
We adopt the API first design with open JHipster option and we wanted to test our API through integration tests.
The UserServiceIT test class throw the following exception :
java.lang.IllegalStateException
at com.github.benmanes.caffeine.jcache.CacheProxy.requireNotClosed(CacheProxy.java:1054)
...
The quick and dirty fix is to add @AutoConfigureMockMvc at the top of the class
@SpringBootTest(classes = CatalogApp.class)
@AutoConfigureMockMvc
@Transactional
public class UserServiceIT {
It works ! But it's weird.
So I have just tested the @henri-tremblay solution and I can confirm it fix the problem.
I am not able to explain the behaviour or the reason of this bug but I can tell this:
Should we do a PR to apply the last solution ?
I'm facing the same problem with EhCache
Cache[usersByLogin] is closed
java.lang.IllegalStateException: Cache[usersByLogin] is closed
what is the state of this ticket ?
@avdev4j @juliensadaoui : I know you encountered this in your projects, right ?
Still waiting your answer @avdev4j @juliensadaoui
finally I suggest to follow the @henri-tremblay advices and add a "clear" case when the createCache method is called.
see https://github.com/jhipster/generator-jhipster/pull/12020
Most helpful comment
Does it happen with other cache implementations? It seems the cache is closed. Which probably means the app context was closed. But then, the same cache is reused. Most probably because it wasn't removed from JCache. So from here, it might be a Cafeine bug, a Spring Cache bug (I doubt it though) or a JHipster bug (maybe). I would check why the cache is closed and then retrieved.