Issue
I have an abstract cache client with an implementation that I'm trying to add unit tests to, and it has a protected class implementation of the key. Like this:
public abstract class SimpleCacheClient<V extends Serializable> {
// Autowired RedissonClient and RedisKeyGenerator
public V get(SimpleCacheKey key) {
// return the cache entry from the autowired RedissonClient
}
public void set(SimpleCacheKey key, V value) {
// set the cache entry
}
public SimpleCacheKey getCacheKey(Object...keys) {
return new SimpleCacheKey(keyGenerator.generateKey(keys));
}
/**
* Simple wrapper for cache key to guarantee that implementations
* are using the key generator provided in this class
*/
protected class SimpleCacheKey {
private String key;
SimpleCacheKey(String key) {
this.key = key;
}
public String getKey() {
return key;
}
@Override
public String toString() {
return getKey();
}
}
}
And here's the implementation I'm trying to test:
public class CacheClientImplementation extends SimpleCacheClient<ArrayList<DateTime>> {
public void addEventDateTimes(String key, ArrayList<DateTime> eventDateTimes) {
// Do stuff with eventDateTimes and then
set(getCacheKey(key), eventDateTimes);
}
public ArrayList<DateTime> getEventDateTimes(String key) {
ArrayList<DateTime> eventDateTimes = get(getCacheKey(key));
// Do stuff with eventDateTimes.
return eventDateTimes;
}
}
I'm trying to test to make sure that CacheClientImplementation
performs certain operations on the values provided to it before setting and getting.
I'm trying to mock the redis cache itself by hijacking the get()
and set()
methods to read and write from/to a HashMap
so that I can check the contents of the "cache" in my tests.
@RunWith(MockitoJUnitRunner.class)
public class CacheClientImplementationTest{
@Mock
private RedissonClient redissonClient;
@Mock
private RedisKeyGenerator redisKeyGenerator;
@Spy
@InjectMocks
private CacheClientImplementation cacheClient = new CacheClientImplementation();
private final HashMap<String, ArrayList<DateTime>> cacheMap = new HashMap<>();
@Before
public void setup() {
Mockito.doAnswer((ver) -> {
cacheMap.put(ver.getArgumentAt(0, Object.class).toString(), ver.getArgumentAt(1, ArrayList.class));
return null;
}).when(cacheClient).set(Mockito.any(), Mockito.any(ArrayList.class));
Mockito.doAnswer((ver) -> cacheMap.getOrDefault(ver.getArgumentAt(0, Object.class).toString(), null))
.when(cacheClient).get(Mockito.any());
}
@After
public void teardown() {
cacheMap.clear();
}
}
However, I end up with this problem when I run a test in the file.
C:\...\CacheClientImplementationTest.java:20: error: SimpleCacheClient.SimpleCacheKey has protected access in SimpleCacheClient
}).when(cacheClient).set(Mockito.any(), Mockito.any(ArrayList.class));
Is there any way I can doAnswer
for these methods without changing SimpleCacheKey?
Thanks!
Solution
This boils down to the visibility of the SimpleCacheKey
class, you simply can't use it from a different package. So Mockito.any()
can't use that class as a return type unless the unit test is in the same package as SimpleCacheClient
.
One solution would be to move your unit test to the same package as SimpleCacheClient
. If this is loaded from a different library that you can't change, you can re-create the same package structure to trick the compiler into thinking the package is the same, giving you access to protected
classes.
But i believe this trick doesn't work with Java 9 modules.
A better solution would be to make a small modification to your CacheClientImplementation
and unit test; encapsulate the part you can't influence and mock that part.
Since you don't really care about the SimpleCacheKey
but just the String key
, the following should work for your intentions:
public class CacheClientImplementation extends SimpleCacheClient<ArrayList<DateTime>> {
public void addEventDateTimes(String key, ArrayList<DateTime> eventDateTimes) {
// Do stuff with eventDateTimes and then
setForKey(key, eventDateTimes);
}
public ArrayList<DateTime> getEventDateTimes(String key) {
ArrayList<DateTime> eventDateTimes = getForKey(key);
// Do stuff with eventDateTimes.
return eventDateTimes;
}
protected ArrayList<DateTime> getForKey(String key) {
return super.get(getCacheKey(key));
}
protected void setForKey(String key, ArrayList<DateTime> value) {
super.set(getCacheKey(key), value);
}
}
And in the unit test you rewrite to the forKey
variants we just created:
Mockito.doAnswer(myAnswer1()).when(cacheClient).setForKey(Mockito.any(), Mockito.any(ArrayList.class));
Mockito.doAnswer(myAnswer2()).when(cacheClient).getForKey(Mockito.any());
I've made the new methods protected
as to not confuse callers which method to use, so in this case the unit test must be in same (test) package as the CacheClientImplementation
.
Answered By - slindenau
Answer Checked By - Gilberto Lyons (JavaFixing Admin)