Spring Boot test пытается инициализировать cache2k во второй раз и завершается неудачно
После добавления cache2k в мой проект некоторые @SpringBootTest
перестал работать с ошибкой:
java.lang.IllegalStateException: кэш уже создан: "кеш"
Ниже я приведу минимальный пример для воспроизведения:
Перейдите на start.spring.io и создайте простейший проект Maven с помощью Cache Starter, затем добавьте зависимости cache2k:
<properties>
<java.version>1.8</java.version>
<cache2k-version>1.2.2.Final</cache2k-version>
</properties>
<dependencies>
<dependency>
<groupId>org.cache2k</groupId>
<artifactId>cache2k-api</artifactId>
<version>${cache2k-version}</version>
</dependency>
<dependency>
<groupId>org.cache2k</groupId>
<artifactId>cache2k-core</artifactId>
<version>${cache2k-version}</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.cache2k</groupId>
<artifactId>cache2k-spring</artifactId>
<version>${cache2k-version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
Теперь настройте самый простой кеш:
@SpringBootApplication
@EnableCaching
public class CachingDemoApplication {
public static void main(String[] args) {
SpringApplication.run(CachingDemoApplication.class, args);
}
@Bean
public CacheManager springCacheManager() {
SpringCache2kCacheManager cacheManager = new SpringCache2kCacheManager();
cacheManager.addCaches(b -> b.name("cache"));
return cacheManager;
}
}
И добавить любой сервис (который мы будем @MockBean
в одном из наших тестов:
@Service
public class SomeService {
public String getString() {
System.out.println("Executing service method");
return "foo";
}
}
Теперь два @SpringBootTest
тесты необходимы для воспроизведения вопроса:
@SpringBootTest
@RunWith(SpringRunner.class)
public class SpringBootAppTest {
@Test
public void getString() {
System.out.println("Empty test");
}
}
@RunWith(SpringRunner.class)
@SpringBootTest
public class WithMockedBeanTest {
@MockBean
SomeService service;
@Test
public void contextLoads() {
}
}
Обратите внимание, что 2-й тест издевался @MockBean
, Это вызывает ошибку (трассировка стека ниже).
Caused by: java.lang.IllegalStateException: Cache already created: 'cache'
at org.cache2k.core.CacheManagerImpl.newCache(CacheManagerImpl.java:174)
at org.cache2k.core.InternalCache2kBuilder.buildAsIs(InternalCache2kBuilder.java:239)
at org.cache2k.core.InternalCache2kBuilder.build(InternalCache2kBuilder.java:182)
at org.cache2k.core.Cache2kCoreProviderImpl.createCache(Cache2kCoreProviderImpl.java:215)
at org.cache2k.Cache2kBuilder.build(Cache2kBuilder.java:837)
at org.cache2k.extra.spring.SpringCache2kCacheManager.buildAndWrap(SpringCache2kCacheManager.java:205)
at org.cache2k.extra.spring.SpringCache2kCacheManager.lambda$addCache$2(SpringCache2kCacheManager.java:143)
at java.util.concurrent.ConcurrentHashMap.compute(ConcurrentHashMap.java:1853)
at org.cache2k.extra.spring.SpringCache2kCacheManager.addCache(SpringCache2kCacheManager.java:141)
at org.cache2k.extra.spring.SpringCache2kCacheManager.addCaches(SpringCache2kCacheManager.java:132)
at com.example.cachingdemo.CachingDemoApplication.springCacheManager(CachingDemoApplication.java:23)
at com.example.cachingdemo.CachingDemoApplication$$EnhancerBySpringCGLIB$$2dce99ca.CGLIB$springCacheManager$0(<generated>)
at com.example.cachingdemo.CachingDemoApplication$$EnhancerBySpringCGLIB$$2dce99ca$$FastClassBySpringCGLIB$$bbd240c0.invoke(<generated>)
at org.springframework.cglib.proxy.MethodProxy.invokeSuper(MethodProxy.java:244)
at org.springframework.context.annotation.ConfigurationClassEnhancer$BeanMethodInterceptor.intercept(ConfigurationClassEnhancer.java:363)
at com.example.cachingdemo.CachingDemoApplication$$EnhancerBySpringCGLIB$$2dce99ca.springCacheManager(<generated>)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiate(SimpleInstantiationStrategy.java:154)
... 52 more
Если вы удалите @MockBean
Оба теста пройдут.
Как я могу избежать этой ошибки в моем наборе тестов?
2 ответа
Поскольку я не хочу никакого пользовательского поведения в тесте, а просто хочу избавиться от этой ошибки, решение состоит в том, чтобы создать CacheManager с использованием уникального имени, такого как это:
@Bean
public CacheManager springCacheManager() {
SpringCache2kCacheManager cacheManager = new SpringCache2kCacheManager("spring-" + hashCode());
cacheManager.addCaches(b -> b.name("cache"));
return cacheManager;
}
Ваш второй тест представляет собой другой ApplicationContext
в целом, поэтому тестовый фреймворк инициирует для него специальный. Если cache2k
с состоянием (например, разделяя CacheManager
для данного загрузчика классов, если он уже существует), второй контекст будет пытаться создать новый CacheManager
пока первый еще активен.
Вам либо нужно пометить один из тестов как грязный (см. @DirtiesContext
) который закроет контекст и закроет CacheManager
или вы можете заменить инфраструктуру кеша опцией, которая не требует всего этого, смотрите @AutoConfigureCache
,
Если cache2k
работает таким образом, что вам нужно испачкать контекст, я настоятельно рекомендую поменять его, используя более поздние опции.
Я столкнулся с той же ошибкой при использовании cache2k с Spring Dev Tools, и в итоге получил следующий код в качестве решения:
@Bean
public CacheManager cacheManager() {
SpringCache2kCacheManager cacheManager = new SpringCache2kCacheManager();
// To avoid the "Caused by: java.lang.IllegalStateException: Cache already created:"
// error when Spring DevTools is enabled and code reloaded
if (cacheManager.getCacheNames().stream()
.filter(name -> name.equals("cache"))
.count() == 0) {
cacheManager.addCaches(
b -> b.name("cache")
);
}
return cacheManager;
}