如何模拟具有私有构造函数参数的方法?

How can I mock a method that has a parameter with a private constructor?

我有一个抽象缓存客户端,它有一个我正在尝试向其添加单元测试的实现,并且它有一个受保护的 class 密钥实现。像这样:

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();
        }
    }
}

这是我要测试的实现:

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;
    }
}

我正在尝试测试以确保 CacheClientImplementation 在设置和获取之前对提供给它的值执行某些操作。 我试图通过劫持 get()set() 方法来模拟 redis 缓存本身来读取和写入 from/to a HashMap 以便我可以检查“缓存”在我的测试中。

@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();
    }
}

但是,当我 运行 在文件中进行测试时,我最终遇到了这个问题。

C:\...\CacheClientImplementationTest.java:20: error: SimpleCacheClient.SimpleCacheKey has protected access in SimpleCacheClient
        }).when(cacheClient).set(Mockito.any(), Mockito.any(ArrayList.class));

有什么方法可以在不更改 SimpleCacheKey 的情况下 doAnswer 这些方法吗?

谢谢!

这归结为 SimpleCacheKey class 的可见性,您根本无法从不同的包中使用它。因此 Mockito.any() 不能将 class 用作 return 类型,除非单元测试与 SimpleCacheClient.

在同一个包中

一个解决方案是将单元测试移动到与 SimpleCacheClient 相同的包中。如果这是从您无法更改的不同库加载的,您可以 re-create 相同的包结构来欺骗编译器认为包是相同的,让您可以访问 protected class是的。 但我相信这个技巧不适用于 Java 9 个模块。

更好的解决方案是对 CacheClientImplementation 和单元测试进行小的修改;封装你无法影响的部分,mock那部分。

由于您并不真正关心 SimpleCacheKey 而只关心 String key,因此以下内容应该符合您的意图:

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);
    }
}

并且在单元测试中,您重写了我们刚刚创建的 forKey 变体:

Mockito.doAnswer(myAnswer1()).when(cacheClient).setForKey(Mockito.any(), Mockito.any(ArrayList.class));
Mockito.doAnswer(myAnswer2()).when(cacheClient).getForKey(Mockito.any());

我已经制作了新方法 protected,以免混淆调用者使用的方法,因此在这种情况下,单元测试必须与 CacheClientImplementation 在相同的(测试)包中。