0

I'm currently unable to use @Cacheble and @Mock/@InjectMocks in the same test class.

To be clear, for using the Mockito.verify(repository, times(1)) I need to @Mock the repository and use the annotation @InjectMock for the repository. Doing it, I can correctly verify the behave of my app, but the result of findById is not cached. In fact the manager.get(cacheName).get(key) will return null.

Instead, if i use the @Autowired annotation, the value is cached but the verify(repository, times(1)) return ZeroInteractions. I checked with the debugger and the behaviour is ok.

What i should do for have both the cache-store and the verify() working? Why is that happening?

[I'm using SpringBoot 2.1.3-release with Caffeine Cache Manager version 3.1.1]

This is the CacheManager class:

@Slf4j
    @Configuration
    @EnableCaching
    public class CaffeineCacheConfig extends CachingConfigurerSupport {
    
        @Bean("cacheManager")
        public CacheManager cacheManager() {
            SimpleCacheManager cacheManager = new SimpleCacheManager(); 
            cacheManager.setCaches(generateCache());
            return cacheManager;
        }
    }

This the Cache:

@Override
        public CaffeineCache generateCache() {
            return new CaffeineCache(name,
                    Caffeine.newBuilder()
                            .maximumSize(15)
                            .expireAfterWrite(60, TimeUnit.MINUTES)
                            .recordStats()
                            .build());
        }

I have a service that im trying to cache which is like:

@Service
@CacheConfig(cacheNames = CacheLocations.APPLICATION_LEVEL)
@Transactional(readOnly = true)
public class ApplicationLevelService implements IApplicationLevelService
{
    @Autowired
    private ApplicationLevelRepository repository;
    @Autowired
    private CacheManager manager;

    @Override
    @Cacheable
    public ApplicationLevel findById(int id)
    {
        return repository.findById(id);
    }
}

Im trying to test it with this class (JUnit5):

@CacheConfig(cacheNames = {APPLICATION_LEVEL})
class ApplicationLevelCacheTest extends AbstractSpringTest
{
    @InjectMocks
    private ApplicationLevelService service;
    @Mock
    private ApplicationLevelRepository repository;
    @Autowired
    private CacheManager cacheManager;

    @BeforeEach
    void evictCache()
    {
        cacheManager.getCache(APPLICATION_LEVEL).clear();
    }

    @Nested
    class TestApplicationLevelCache
    {
        @Test
        @DisplayName("findAById() sets the resulting list in '" + CacheLocations.APPLICATION_LEVEL + "' cache")
        void testApplicationLevelCaching_ApplicationLevelsAreCached_FindById()
        {
            when(repository.findById(anyInt())).thenReturn(Optional.of(new ApplicationLevel("mocked")));

            assertNotNull(cacheManager.getCache(CacheLocations.APPLICATION_LEVEL));

            var expected = service.findById(1);
            verify(repository, times(1)).findById(anyInt());

            assertNotNull(cacheManager.getCache(APPLICATION_LEVEL).get(1));

            // should be cached
            var actual = service.findById(1);
            verifyZeroInteractions(repository);
            assertEquals(expected, actual);
        }
    }
}

where AbstractSpringTest is just the class containing:

  • @SpringJUnitConfig
  • @SpringBootTest()
  • @ActiveProfiles("test")

2 Answers2

1

Testing @Cacheable requires @SpringBootTest to bootstrap the entire container. This means this is an integration test you wrote, going through many layers of the application.

In this case, prefer @Autowired/@MockBean to @InjectMock/@Mock (you can use keep @InjectMock/@Mock for unit tests).

https://docs.spring.io/spring-boot/docs/current/api/org/springframework/boot/test/mock/mockito/MockBean.html

Try replacing:

@InjectMocks ApplicationLevelService service;
@Autowired CacheManager cacheManager;
@Mock ApplicationLevelRepository repository;

With:

@Autowired ApplicationLevelService service;
@Autowired CacheManager cacheManager;
@MockBean ApplicationLevelRepository repository;
0

The verify() method can be used to verify the number of interactions of a bean that has been mocked with Mockito.

Using the @Autowired annotation you have initialized the bean by taking it directly from the Spring container, using Spring's D.I. Your cacheManager object is outside the context of Mockito. It has not been mocked with Mockito.

I think you can solve this problem by using the @Spy annotation on the cacheManager object.

@Spy and @Mock can be both used to mock fields. The difference is that with the @Mock annotation, you create a complete mock or dummy object, whereas with @Spy, you have the real object and can directly call or mock specific methods of it.

You can try with:

@Spy
CacheManager cacheManager = new SimpleCacheManager();

Hope this help.

harry-potter
  • 1,981
  • 5
  • 29
  • 55
  • just with "@Spy@Autowired protected CacheManager cacheManager" im able to run it without nullPointer in the manager.evict() part. But the cache still not working. The value is not cached. – E.Cecchetti Oct 21 '22 at 14:40
  • @E.Cecchetti Can you try instantiating the cacheManager object? I just edited my answer. – harry-potter Oct 21 '22 at 15:19
  • I've tried by instatiating it (not working, also because i need some caches ad hoc for it to work) and also by defining that as #Autowired#Spy and #Autowired#InjectWired in the Service class. The cache is recognized and works, the mock also, but the verify still not consider the calls. Seems that the verify cannot recognize my repo (like it is proxied or something similar) and it does not consider his iterations. – E.Cecchetti Oct 24 '22 at 08:18