3
    @Component
    public class LibraryService {

        @Autowired
        private BookService bookService;

        private Cache<UUID, Book> bookCache = CacheBuilder.newBuilder().maximumSize(512).expireAfterWrite(15, TimeUnit.MINUTES).build();

        public void someMethod(UUID bookId) {
          try {
            Book book = bookCache.get(bookId, () -> bookService.findBookByUuid(bookId));
            //some operations
          } catch (ExecutionException e) {
            throw new ProcessingFailureException("Failed to load cache value", e);
          }

         }

    }

I need to write unit test for this class so that I tried to mock the Google Guava cache as following.

public class LibraryServiceTest {

    @InjectMocks
    private LibraryService service;

    @Mock
    private BookService bookService;

    @Mock
    private Cache<UUID, Book> bookCache;

    @Before
    public void initialize() {
        MockitoAnnotations.initMocks(this);
    }

    @Test
    public void testMethod() throws ExecutionException {
        UUID bookId = UUID.randomUUID();
        Book book = new Book();

        when(bookCache.get(bookId, () -> bookService.findBookByUuid(bookId))).thenReturn(book);

        service.someMethod(bookId);
    }
}

I got some NullPointer Exception.

com.google.common.util.concurrent.UncheckedExecutionException: java.lang.NullPointerException
    at com.google.common.cache.LocalCache$Segment.get(LocalCache.java:2203)
    at com.google.common.cache.LocalCache.get(LocalCache.java:3937)
    at com.google.common.cache.LocalCache$LocalManualCache.get(LocalCache.java:4739)
Caused by: java.lang.NullPointerException

Note: I know, I can change the method some testable way. In this case I couldn't do that.

Is there any way to mock this book cache?

Damith Ganegoda
  • 4,100
  • 6
  • 37
  • 46
  • Does `@InjectMocks` overwrite random, already assigned, private variables? You need to be thinking in more IoC terms - a `Cache` is a **dependency** of `LibraryService` - it should therefore be interfaced away and injected in. In fact, as you are using Spring, may I suggest [Spring Cache](https://docs.spring.io/spring/docs/current/spring-framework-reference/html/cache.html) - supports Guava `Cache` as a backend so functionally your code would be the same. – Boris the Spider Jan 10 '17 at 12:19
  • Yeah, I understand but thing is I couldn't change the implementation at this moment. – Damith Ganegoda Jan 10 '17 at 12:23
  • Could you not test that `BookService` is not called again for a second attempt to load a supposedly cached book? The way this class is written, `Cache` is a private implementation detail and should not be tested. The expected behaviour should be. See also [`Ticker`](https://github.com/google/guava/wiki/CachesExplained#testing-timed-eviction) - but if you cannot change any code you are out of luck on that front too. – Boris the Spider Jan 10 '17 at 12:25
  • yeah, I think so. I could skip unit testing for this method and later change it and write it. – Damith Ganegoda Jan 10 '17 at 12:30

1 Answers1

4

Your code should work if you better mock the get method call as next to ensure arguments match such that you will get the expected book instance as result.

import static org.mockito.Matchers.any;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.when;
...

public class LibraryServiceTest {
    ...

    @Test
    public void testMethod() throws ExecutionException {
        ...
        when(bookCache.get(eq(bookId), any(Callable.class))).thenReturn(book);
        ...
    }
}
Nicolas Filotto
  • 43,537
  • 11
  • 94
  • 122