How to mock HashOperations of Spring StringRedisTemplate?

I'm writing unit test for my CacheService class:

public class CacheService {
    private final HashOperations<String, String, String> redisHashOps;
    private final ValueOperations<String, String> valueOps;
    private final SetOperations<String, String> setOps;


    public CacheService(StringRedisTemplate redisTemplate) {
        this.redisHashOps = redisTemplate.opsForHash();
        this.valueOps = redisTemplate.opsForValue();
        this.setOps = redisTemplate.opsForSet();
    }

Unit test:

public class CacheServiceTest {

    private CacheService cacheService;

    private HashOperations<String, String, String> redisHashOps;
    private ValueOperations<String, String> redisValueOps;
    private SetOperations<String, String> redisSetOps;

    @BeforeEach
    public void setUp() {
        final StringRedisTemplate redisTemplate = mock(StringRedisTemplate.class);

        redisValueOps = mock(ValueOperations.class);
        redisHashOps = mock(HashOperations.class);
        redisSetOps = mock(SetOperations.class);

        when(redisTemplate.opsForValue()).thenReturn(redisValueOps);
        when(redisTemplate.opsForHash()).thenReturn(redisHashOps);
        when(redisTemplate.opsForSet()).thenReturn(redisSetOps);

redisValueOps and redisSetOps have no problem, but for redisHashOps, the IDE prompts: IDE prompts

If I build the project will see following error:

java: no suitable method found for thenReturn(org.springframework.data.redis.core.HashOperations<java.lang.String,java.lang.String,java.lang.String>)
    method org.mockito.stubbing.OngoingStubbing.thenReturn(org.springframework.data.redis.core.HashOperations<java.lang.String,java.lang.Object,java.lang.Object>) is not applicable
      (argument mismatch; org.springframework.data.redis.core.HashOperations<java.lang.String,java.lang.String,java.lang.String> cannot be converted to org.springframework.data.redis.core.HashOperations<java.lang.String,java.lang.Object,java.lang.Object>)
    method org.mockito.stubbing.OngoingStubbing.thenReturn(org.springframework.data.redis.core.HashOperations<java.lang.String,java.lang.Object,java.lang.Object>,org.springframework.data.redis.core.HashOperations<java.lang.String,java.lang.Object,java.lang.Object>...) is not applicable
      (argument mismatch; org.springframework.data.redis.core.HashOperations<java.lang.String,java.lang.String,java.lang.String> cannot be converted to org.springframework.data.redis.core.HashOperations<java.lang.String,java.lang.Object,java.lang.Object>)

Looking at RedisTemplate's source code, I found the difference between opsForHash and opsForValue:

public <HK, HV> HashOperations<K, HK, HV> opsForHash() ...
public ValueOperations<K, V> opsForValue() ...

How to modify the unit test to make it working?


Solution 1:

You can rewrite your mock expectations using doReturn instead:

doReturn(redisHashOps).when(redisTemplate).opsForHash();

Solution 2:

You found it right:

In the first (and last) case <K, V> refers to RedisTemplate generic parameters (which are assigned to <String, String> here).

In case of <HK, HV> HashOperations<K, HK, HV>, the K is "safe again", but <HK, HV> are "method generic parameters", which the compiler cannot infer here.

Solutions:

  • as armandino correctly proposed with:
    doReturn(redisHashOps).when(redisTemplate).opsForHash();
    
    we free the compiler from guessing.
  • or by passing the "generic arguments":
    when(redisTemplate.<String,String>opsForHash()).thenReturn(redisHashOps);
    
  • or the "raw"/brutal approach:
    private HashOperations<String, Object, Object> redisHashOps;
    ...
    when(redisTemplate.opsForHash()).thenReturn(redisHashOps);