-
Notifications
You must be signed in to change notification settings - Fork 41.7k
Description
Problem
While Upgrading from Spring Boot 3.5.8 to 4.0.0, I'm seeing minimal @WebMvcTest tests and other test slices (e.g. @DataJdbcTest) fail with the following exception.
No qualifying bean of type 'org.springframework.cache.CacheManager' available: no CacheResolver specified - register a CacheManager bean or remove the @EnableCaching annotation from your configuration.
org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'org.springframework.cache.CacheManager' available: no CacheResolver specified - register a CacheManager bean or remove the @EnableCaching annotation from your configuration.
at app//org.springframework.cache.interceptor.CacheAspectSupport.afterSingletonsInstantiated(CacheAspectSupport.java:287)
at app//org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:1147)
at app//org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:983)
at app//org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:620)
at app//org.springframework.boot.SpringApplication.refresh(SpringApplication.java:765)
at app//org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:454)
at app//org.springframework.boot.SpringApplication.run(SpringApplication.java:321)
Example
I've included the abbreviated example below to demonstrate the problem.
@WebMvcTest(ProductController.class)
class ProductControllerTest {
@Autowired
private MockMvc mockMvc;
@Autowired
private ObjectMapper objectMapper;
@MockitoBean
private ProductService productService;
@Test
void getProduct_WhenProductExists_ReturnsProduct() throws Exception {
Product product = new Product(1L, "Test Product", "Test Description", 99.99);
when(productService.getProductById(1L)).thenReturn(Optional.of(product));
mockMvc.perform(get("/api/products/1"))
.andExpect(status().isOk())
.andExpect(content().contentType(MediaType.APPLICATION_JSON))
.andExpect(jsonPath("$.id").value(1))
.andExpect(jsonPath("$.name").value("Test Product"))
.andExpect(jsonPath("$.description").value("Test Description"))
.andExpect(jsonPath("$.price").value(99.99));
}
}
@RestController
@RequestMapping("/api/products")
public class ProductController {
private final ProductService productService;
public ProductController(ProductService productService) {
this.productService = productService;
}
@GetMapping("/{id}")
public ResponseEntity<Product> getProduct(@PathVariable Long id) {
return productService.getProductById(id)
.map(ResponseEntity::ok)
.orElse(ResponseEntity.notFound().build());
}
}
@Service
public class ProductService {
private final Map<Long, Product> dataStore = new ConcurrentHashMap<>();
private final AtomicLong idGenerator = new AtomicLong(1);
public ProductService() {
dataStore.put(1L, new Product(1L, "Sample Product", "A sample product", 99.99));
dataStore.put(2L, new Product(2L, "Another Product", "Another sample", 149.99));
idGenerator.set(3);
}
@Cacheable(value = "products", key = "#id")
public Optional<Product> getProductById(Long id) {
System.out.println("Fetching product from data store for id: " + id);
return Optional.ofNullable(dataStore.get(id));
}
}
@Configuration
public class CacheConfig {
@Bean
public CacheManager cacheManager() {
return new ConcurrentMapCacheManager("products");
}
}
@EnableCaching
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}Full Reproducer:
- Spring boot 3.5.8 - test, which passes.
- Spring boot 4.0.0 - test, which fails.
What appears to be happening is these test slices scan the @SpringBootApplication to pick up configurations and sees the @EnableCacheing and then aggressively tries to autoconfigure cacheing.
In Spring Boot 3.x, if cacheing was not configured, it would silently ignore @Cacheable annotation. But in Spring Boot 4.x it fails the test.
Expectation
OSS tests slices and minimal test slices should not fail when trying to configure AOP integrations like cacheing when the dependencies and configurations are provided not provided those annotations to work.
The workaround is move @EnableCacheing to another @Configuration so it's not picked up by OSS Test slice annotations. However, this is not ideal or intuitive for the user to be aware of delicate separations like this. This is one example, but there's probably many ways this fails if @EnableCacheing is seen in a minimal test, but we don't want to intentionally test Cacheing.