generator-jhipster: Error while using @MockBean in integration test

Overview of the issue

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(<generated>)
	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(<generated>)
	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)
Motivation for or Use Case

Using @MockBean is really powerful to test spring boot applications it simplifies the bean and mock creations in SpringBootTest

Reproduce the error

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

Related issues

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

Suggest a Fix

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

JHipster Version(s)

I am using 6.4.1 version with caffeine cache

JHipster configuration
.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": []
  }
}
Entity configuration(s) entityName.json files generated in the .jhipster directory

You can get all the informations in the repo

Browsers and Operating System

Using windows 10

  • Checking this box is mandatory (this is just to show you read everything)

About this issue

  • Original URL
  • State: closed
  • Created 5 years ago
  • Comments: 17 (17 by maintainers)

Most upvoted comments

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.

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.

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 have dug in it a little bit. The problem seems to be that

  1. @MockBean taint the Spring context in some way
  2. So Spring partially recreate it. I’m not sure at which level
  3. This causes CacheConfiguration to be run again
  4. The same (JCache) CacheManager is retrieved, which is expected
  5. All the caches are in it. The code in CacheConfiguration destroys the cache and create a new one. All wrapped under a new JCacheCacheManager
  6. But the old JCacheCacheManager are staying a dependency to other beans like UserService
  7. So when we use this old JCacheCacheManager, 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.

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 tried adding “spring.cache.type=none” to test application.yml but it did not work.

Additionally, I added a condition on CacheConfiguration:

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


@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