/** * Copyright 2016-2020 Sixhours * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.sixhours.memcached.cache; import net.rubyeye.xmemcached.MemcachedClient; import org.junit.After; import org.junit.Before; import org.junit.Test; import org.springframework.cache.Cache; import org.springframework.cache.support.NullValue; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.mockito.Mockito.*; /** * Memcached cache tests. * * @author Igor Bolic */ public class MemcachedCacheTest { private static final String CACHED_OBJECT_KEY = "cached_value_key"; private static final String CACHE_NAME = "cache"; private static final String CACHE_PREFIX = Default.PREFIX; private static final int CACHE_EXPIRATION = Default.EXPIRATION; private static final String CACHED_KEY_REGEX = String.format("%s:.+:%s", CACHE_PREFIX, CACHED_OBJECT_KEY); private static final String NAMESPACE_KEY = Default.NAMESPACE; private static final String NAMESPACE_KEY_VALUE = String.valueOf(System.currentTimeMillis()); private IMemcachedClient memcachedClient; private MemcachedCache memcachedCache; private final Object cachedValue = new Object(); private final Object nullCachedValue = NullValue.INSTANCE; private final Object newCachedValue = new Object(); private final Object valueLoaderValue = new Object(); private final Object valueLoaderNullValue = NullValue.INSTANCE; private String memcachedKey; private String namespaceKey; @Before public void setUp() { memcachedClient = mock(IMemcachedClient.class); memcachedCache = new MemcachedCache(CACHE_NAME, memcachedClient, CACHE_EXPIRATION, CACHE_PREFIX, NAMESPACE_KEY); memcachedKey = String.format("%s:%s:%s:%s", CACHE_PREFIX, CACHE_NAME, NAMESPACE_KEY_VALUE, CACHED_OBJECT_KEY); namespaceKey = String.format("%s:%s:%s", CACHE_PREFIX, CACHE_NAME, NAMESPACE_KEY); } @After public void tearDown() { verifyNoMoreInteractions(memcachedClient); } @Test public void whenLookupThenCallMemcachedClientGetKey() { when(memcachedClient.get(any())).thenReturn(NAMESPACE_KEY_VALUE).thenReturn(cachedValue); Object actual = memcachedCache.lookup(CACHED_OBJECT_KEY); assertThat(actual).isEqualTo(cachedValue); verify(memcachedClient).get(memcachedKey); verify(memcachedClient).get(namespaceKey); } @Test public void whenLookupThenIncrementHits() { when(memcachedClient.get(any())).thenReturn(NAMESPACE_KEY_VALUE).thenReturn(cachedValue); assertThat(memcachedCache.hits()).isEqualTo(0); assertThat(memcachedCache.misses()).isEqualTo(0); memcachedCache.lookup(CACHED_OBJECT_KEY); assertThat(memcachedCache.hits()).isEqualTo(1); assertThat(memcachedCache.misses()).isEqualTo(0); verify(memcachedClient).get(memcachedKey); verify(memcachedClient).get(namespaceKey); } @Test public void whenLookupAndCacheValueMissingThenIncrementMisses() { when(memcachedClient.get(any())).thenReturn(NAMESPACE_KEY_VALUE).thenReturn(null); assertThat(memcachedCache.hits()).isEqualTo(0); assertThat(memcachedCache.misses()).isEqualTo(0); memcachedCache.lookup(CACHED_OBJECT_KEY); assertThat(memcachedCache.hits()).isEqualTo(0); assertThat(memcachedCache.misses()).isEqualTo(1); verify(memcachedClient).get(memcachedKey); verify(memcachedClient).get(namespaceKey); } @Test public void whenGetNameThenReturnCacheName() { String actual = memcachedCache.getName(); assertThat(actual).isEqualTo(CACHE_NAME); } @Test public void whenGetNativeThenReturnMemcachedClient() { final MemcachedClient client = mock(MemcachedClient.class); when(memcachedClient.nativeCache()).thenReturn(client); MemcachedClient actual = (MemcachedClient) memcachedCache.getNativeCache(); assertThat(actual).isSameAs(client); verify(memcachedClient).nativeCache(); } @Test public void whenGetWithValueLoaderThenReturnCachedValue() { when(memcachedClient.get(anyString())) .thenReturn(NAMESPACE_KEY_VALUE) .thenReturn(cachedValue); Object actual = memcachedCache.get(CACHED_OBJECT_KEY, () -> valueLoaderValue); assertThat(actual).isEqualTo(cachedValue); verify(memcachedClient).get(namespaceKey); verify(memcachedClient).get(memcachedKey); } @Test public void whenGetWithValueLoaderAndNullCachedValueThenReturnNull() { when(memcachedClient.get(anyString())) .thenReturn(NAMESPACE_KEY_VALUE) .thenReturn(nullCachedValue); Object actual = memcachedCache.get(CACHED_OBJECT_KEY, () -> valueLoaderValue); assertThat(actual).isEqualTo(null); verify(memcachedClient).get(namespaceKey); verify(memcachedClient).get(memcachedKey); } @Test public void whenGetWithValueLoaderAndCachedValueFromSecondLookupThenReturnCachedValue() { when(memcachedClient.get(namespaceKey)).thenReturn(NAMESPACE_KEY_VALUE); when(memcachedClient.get(memcachedKey)).thenReturn(null).thenReturn(cachedValue); Object actual = memcachedCache.get(CACHED_OBJECT_KEY, () -> valueLoaderValue); assertThat(actual).isEqualTo(cachedValue); verify(memcachedClient, times(2)).get(namespaceKey); verify(memcachedClient, times(2)).get(memcachedKey); } @Test public void whenGetWithValueLoaderAndNullCachedValueFromSecondLookupThenReturnNull() { when(memcachedClient.get(namespaceKey)).thenReturn(NAMESPACE_KEY_VALUE); when(memcachedClient.get(memcachedKey)).thenReturn(null).thenReturn(nullCachedValue); Object actual = memcachedCache.get(CACHED_OBJECT_KEY, () -> valueLoaderValue); assertThat(actual).isEqualTo(null); verify(memcachedClient, times(2)).get(namespaceKey); verify(memcachedClient, times(2)).get(memcachedKey); } @Test public void whenGetWithValueLoaderAndCachedValueMissingThenReturnValueLoaderNull() { when(memcachedClient.get(namespaceKey)).thenReturn(NAMESPACE_KEY_VALUE); when(memcachedClient.get(memcachedKey)).thenReturn(null); Object actual = memcachedCache.get(CACHED_OBJECT_KEY, () -> valueLoaderNullValue); assertThat(actual).isEqualTo(null); verify(memcachedClient, times(3)).get(namespaceKey); verify(memcachedClient, times(2)).get(memcachedKey); verify(memcachedClient).set(memcachedKey, CACHE_EXPIRATION, valueLoaderNullValue); verify(memcachedClient).touch(namespaceKey, CACHE_EXPIRATION); } @Test public void whenGetWithValueLoaderAndCachedValueMissingThenReturnValueLoaderValue() { when(memcachedClient.get(namespaceKey)).thenReturn(NAMESPACE_KEY_VALUE); when(memcachedClient.get(memcachedKey)).thenReturn(null); Object actual = memcachedCache.get(CACHED_OBJECT_KEY, () -> valueLoaderValue); assertThat(actual).isEqualTo(valueLoaderValue); verify(memcachedClient, times(3)).get(namespaceKey); verify(memcachedClient, times(2)).get(memcachedKey); verify(memcachedClient).set(memcachedKey, CACHE_EXPIRATION, valueLoaderValue); verify(memcachedClient).touch(namespaceKey, CACHE_EXPIRATION); } @Test public void whenGetWithValueLoaderThrowsExceptionThenValueRetrievalException() { when(memcachedClient.get(namespaceKey)) .thenReturn(NAMESPACE_KEY_VALUE) .thenReturn(null); assertThatThrownBy(() -> memcachedCache.get(CACHED_OBJECT_KEY, () -> { throw new Exception("exception to be wrapped"); })) .isInstanceOf(Cache.ValueRetrievalException.class) .hasFieldOrPropertyWithValue("key", CACHED_OBJECT_KEY); verify(memcachedClient, times(2)).get(namespaceKey); verify(memcachedClient, times(2)).get(matches(CACHED_KEY_REGEX)); verify(memcachedClient).set(eq(namespaceKey), eq(CACHE_EXPIRATION), anyString()); } @Test public void whenPutNullThenStoreNullValueInstance() { when(memcachedClient.get(namespaceKey)).thenReturn(NAMESPACE_KEY_VALUE); memcachedCache.put(CACHED_OBJECT_KEY, null); verify(memcachedClient).get(namespaceKey); verify(memcachedClient).set(memcachedKey, CACHE_EXPIRATION, NullValue.INSTANCE); verify(memcachedClient).touch(namespaceKey, CACHE_EXPIRATION); } @Test public void whenPutAndNamespaceMissingThenSetNamespace() { when(memcachedClient.get(namespaceKey)).thenReturn(null); memcachedCache.put(CACHED_OBJECT_KEY, cachedValue); verify(memcachedClient).get(namespaceKey); verify(memcachedClient).set(eq(namespaceKey), eq(CACHE_EXPIRATION), anyString()); verify(memcachedClient).set(endsWith(CACHED_OBJECT_KEY), eq(CACHE_EXPIRATION), eq(cachedValue)); verify(memcachedClient).touch(namespaceKey, CACHE_EXPIRATION); } @Test public void whenPutIfAbsentThenReturnExistingValue() { when(memcachedClient.get(anyString())).thenReturn(NAMESPACE_KEY_VALUE).thenReturn(cachedValue); Cache.ValueWrapper actual = memcachedCache.putIfAbsent(CACHED_OBJECT_KEY, newCachedValue); assertThat(actual.get()).isEqualTo(cachedValue); verify(memcachedClient).get(namespaceKey); verify(memcachedClient).get(memcachedKey); } @Test public void whenPutIfAbsentAndNoCachedValueThenReturnNewValue() { when(memcachedClient.get(anyString())) .thenReturn(NAMESPACE_KEY_VALUE) .thenReturn(null) .thenReturn(NAMESPACE_KEY_VALUE); Cache.ValueWrapper actual = memcachedCache.putIfAbsent(CACHED_OBJECT_KEY, newCachedValue); assertThat(actual.get()).isEqualTo(newCachedValue); verify(memcachedClient, times(2)).get(namespaceKey); verify(memcachedClient).get(memcachedKey); verify(memcachedClient).set(eq(memcachedKey), anyInt(), eq(newCachedValue)); verify(memcachedClient).touch(namespaceKey, CACHE_EXPIRATION); } @Test public void whenEvictThenMemcachedClientDelete() { when(memcachedClient.get(anyString())).thenReturn(NAMESPACE_KEY_VALUE); memcachedCache.evict(CACHED_OBJECT_KEY); verify(memcachedClient).get(namespaceKey); verify(memcachedClient).delete(memcachedKey); } @Test public void whenClearThenMemcachedClientIncrNamespace() { memcachedCache.clear(); verify(memcachedClient).incr(namespaceKey, 1); } }