JUnit/Mockito How to test that a method is called, but by a different object

I’m trying to test that a cache I created is working properly, and duplicate calls to a method aren’t actually called multiple times, but loaded from my cache.

I’m using LoadingCache from Google Guava to accomplish this.

So my naive approach was to create a spy, and verify that the method was called once. I then figured out that because spy() is a decorator, I can only see if the method was called on that object. Since the method is being called by LoadingCache, my spy cannot verify it.

How do I best test that my cache is being used properly?

(note: I could make a LoadingCache dependency, and check that the correct method is called, but then I don’t know if my cache is working. Maybe the hash is calculated in a way I didn’t anticipate, and so it actually is calling the method every time. I want to actually see the effect)

private final LoadingCache<TaskDetails, byte[]> cache;

    ...

    cache = CacheBuilder.newBuilder()
                .maximumSize(cacheSize)
                .expireAfterAccess(Duration.ofMinutes(cacheDurationInMinutes))
                .build(CacheLoader.from(this::doTask));
public byte[] taskCaller(...) {
    ...
    return cache.getUnchecked();
}
public byte[] doTask(...) {
    if(something.doSomething() ... ) {
    ...
}

First test attempt doesn’t work, Mockito says: Wanted but not invoked (the method was “never called”, since the spy doesn’t know the cache called it)

@Test
public void test() {
    // given
    TaskService spy = spy(
        new TaskService(...)
    );

    // when
    for (int i = 0; i < 3; i++) {
        spy.taskCaller(...);
    }

    // then
    //if caching is working, we should only do the real work once
    verify(spy, times(1)).doTask(any(TaskDetails.class));
}

Second attempt: NullPointerException on first statement in the method ( something.doSomething() ), I’m not completely sure why, but I’m convinced for the same reason as before

@Test
public void test() {
    // given
    TaskService spy = spy(
        new TaskService(...)
    );
    final int[] counter = {0};
    when(spy.doTask(any())).thenAnswer(invocation -> {
        counter[0]++;
        return new byte[]{0,0,0,0};
    });

    // when
    int run = 3;
    for (int i = 0; i < run; i++) {
        spy.taskCaller(...);
    }

    // then
    //if caching is working, we should only do the real work once
    assertThat(run).isEqualTo(counter[0]);
    verify(spy, times(1)).doTask(any(TaskDetails.class));
}

Answer

Two ideas:

  1. Enable cache stats recording, interact with the cache, get the cache stats, then verify that the load count (and possibly other stats) are what you expect.
  2. Make your CacheLoader a separate named class rather than a method reference, an instance of which is passed to CacheBuilder.build(), and arrange for that instance to be a mock in your test. You can then verify method call counts and arguments on your mock loader.