3

Note: I already look at and tried some approaches on SO e.g. How to test Spring's declarative caching support on Spring Data repositories?, but as most of them old, I cannot make them work properly and I need a solution with the latest library versions. So, I would be appreciated if you have a look at the question and help.

@Service
@EnableCaching
@RequiredArgsConstructor
public class DemoServiceImpl implements DemoService {

    private static final String CACHE_NAME = "demoCache";

    private final LabelRepository labelRepository;
    private final LabelTranslatableRepository translatableRepository;
    private final LanguageService languageService;


    @Override
    public LabelDTO findByUuid(UUID uuid) {
        final Label label = labelRepository.findByUuid(uuid)
                .orElseThrow(() -> new EntityNotFoundException("Not found."));
        final List<LabelTranslatable> translatableList = translatableRepository.findAllByEntityUuid(uuid);
        return new LabelDTO(Pair.of(label.getUuid(), label.getKey()), translatableList);
    }
}

I created the following Unit Test to test caching for the nethod above:

@EnableCaching
@ImportAutoConfiguration(classes = {
        CacheAutoConfiguration.class,
        RedisAutoConfiguration.class
})
@ExtendWith(MockitoExtension.class)
class TextLabelServiceImpl_deneme_Test {

    @Autowired
    private CacheManager cacheManager;

    @InjectMocks
    private LabelService labelService;

    @Mock
    private LabelRepository labelRepository;

    @Mock
    private LabelTranslatableRepository translatableRepository;

    @Test
    void test_Cache() {
        UUID uuid = UUID.randomUUID();
        final TextLabel textLabel = new TextLabel();
        textLabel.setId(1);
        textLabel.setKey("key1");

        TextLabelTranslatable textLabelTranslatable = new TextLabelTranslatable();
        textLabelTranslatable.setEntityUuid(uuid);
        textLabelTranslatable.setLanguage(SupportedLanguage.fr);
        textLabelTranslatable.setValue("value1");

        final List<TextLabelTranslatable> translatableList = new ArrayList<>();
        translatableList.add(textLabelTranslatable);

        when(labelRepository.findByUuid(uuid)).thenReturn(Optional.of(textLabel));
        when(translatableRepository.findAllByEntityUuid(uuid)).thenReturn(translatableList);

        TextLabelDTO result1 = labelService.findByUuid(uuid);
        TextLabelDTO result2 = labelService.findByUuid(uuid);

        assertEquals(result1, result2);
        Mockito.verify(translatableRepository, Mockito.times(1)).findAllByEntityUuid(uuid);
    }

I am not sure if there is a missing part in my test, but at the last line (Mockito.verify()), it returns 2 instead of 1 that means caching not works. But it is working properly and there is a problem in my test I think. How should I complete the unit test to check the caching properly?

Jack
  • 1
  • 21
  • 118
  • 236

1 Answers1

0

You need to annotate the service class method with @Cacheable. Try to follow the code in this tutorial. The following test code works as expected

@Import({CacheConfig.class, DemoServiceImpl.class})
@ExtendWith(SpringExtension.class)
@EnableCaching
@ImportAutoConfiguration(classes = {
    CacheAutoConfiguration.class,
    RedisAutoConfiguration.class
})
class DemoServiceImplTest {

  @MockBean
  private LabelRepository labelRepository;

  @Autowired
  private DemoServiceImpl demoService;

  @Autowired
  private CacheManager cacheManager;

  @TestConfiguration
  static class EmbeddedRedisConfiguration {

    private final RedisServer redisServer;

    public EmbeddedRedisConfiguration() {
      this.redisServer = new RedisServer();
    }

    @PostConstruct
    public void startRedis() {
      redisServer.start();
    }

    @PreDestroy
    public void stopRedis() {
      this.redisServer.stop();
    }
  }

  @Test
  void givenRedisCaching_whenFindItemById_thenItemReturnedFromCache() {
    UUID id = UUID.randomUUID();
    Label aLabel = new Label(id, "label");

    Mockito.when(labelRepository.findById(id)).thenReturn(Optional.of(aLabel));

    Label labelCacheMiss = demoService.findByUuid(id);
    Label labelCacheHit = demoService.findByUuid(id);

    Mockito.verify(labelRepository, Mockito.times(1)).findById(id);
  }
}

With this service class code:

@Service
@RequiredArgsConstructor
@EnableCaching
public class DemoServiceImpl {

  public static final String CACHE_NAME = "demoCache";

  private final LabelRepository labelRepository;

  @Cacheable(value = CACHE_NAME)
  public Label findByUuid(UUID uuid) {
    return labelRepository.findById(uuid)
        .orElseThrow(() -> new EntityNotFoundException("Not found."));
  }
}

And this CacheConfig:

@Configuration
public class CacheConfig {

  @Bean
  public RedisCacheManagerBuilderCustomizer redisCacheManagerBuilderCustomizer() {
    return (builder) -> builder
        .withCacheConfiguration(DemoServiceImpl.CACHE_NAME,
            RedisCacheConfiguration.defaultCacheConfig().entryTtl(Duration.ofMinutes(10)));
  }

  @Bean
  public RedisCacheConfiguration cacheConfiguration() {
    return RedisCacheConfiguration.defaultCacheConfig()
        .entryTtl(Duration.ofMinutes(60))
        .disableCachingNullValues()
        .serializeValuesWith(
            RedisSerializationContext.SerializationPair.fromSerializer(
                new GenericJackson2JsonRedisSerializer()));
  }
}
Mustafa
  • 5,624
  • 3
  • 24
  • 40
  • **#1** Thanks a lot, I have just found time to have a look at. First, yes I already used `@Cacheable()` in my service method, but did not add to the code in question. On the other hand, I am not sure if I have to use `CacheConfig` to test caching in my Unit Test. Normally it is good, but we did not use it and if I do not need for testing, I do not want to add it. – Jack Sep 24 '21 at 07:37
  • **#2** ** Does `@ImportAutoConfiguration(classes = { CacheAutoConfiguration.class, RedisAutoConfiguration.class })` make the default settings (config) for the part you post in the `CacheConfig` ? I think if we do not use `CacheConfig` class, we may use `@ImportAutoConfiguration()` annotation. Am I wrong? – Jack Sep 24 '21 at 07:39
  • @Robert, the `ImportAutoConfiguration` stuff is being done automatically by springboot based on the classes in your classpath. You add it here for the test. The `CacheConfig` stuff is customizing the defaults. If your caching works without this customization the you can do without it. – Mustafa Sep 24 '21 at 10:43
  • Yes, I realized that on the page that you suggested (Spring Boot Cache with Redis) and I also followed that example for my implementation. However, I cannot perform the caching. There is a mistake that I can still not fix. Could you please have a look at **[Cannot test Spring Caching in Unit Test](https://stackoverflow.com/questions/69313791/cannot-test-spring-caching-in-unit-test)**? – Jack Sep 24 '21 at 10:54