#java #junit #mockito #ehcache
#java #junit #mockito #ehcache
Вопрос:
Я пишу модульные тесты для сервиса, который хочу протестировать. Несколько методов пытаются извлечь значения из EhCache
. Я пытался издеваться над ними с помощью Mockito
и просто использовал get(String key)
метод Cache
return null, поскольку я хочу игнорировать кэширование для этих тестов.
Мой тестовый класс:
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyList;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import java.util.Arrays;
import java.util.List;
import javax.annotation.Resource;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import com.jysk.dbl.esldataservice.model.Preis;
import com.jysk.dbl.esldataservice.service.PreisService;
import com.jysk.dbl.esldataservice.service.external.PimDataService;
import com.jysk.dbl.esldataservice.service.external.SapCarService;
import net.sf.ehcache.Cache;
import net.sf.ehcache.CacheManager;
public class PreisServiceTest {
@Mock
private SapCarService sapCarService;
@Mock
private ArticleDataService articleDataService;
@Mock
private CacheManager cacheManager;
@Mock
private Cache cache;
@InjectMocks
@Resource
private PreisService preisService;
@BeforeEach
public void setup() {
MockitoAnnotations.initMocks(this);
when(this.cacheManager.getCache(anyString())).thenReturn(this.cache);
when(this.cache.get(anyString())).then(null);
}
protected static final String TEST_STORE_IDENTIFIER = "1234";
private static final String ARTICLE_IDENTIFIER_1 = "12345001";
private static final String ARTICLE_IDENTIFIER_2 = "54321001";
private final Preis p1 = new Preis(ARTICLE_IDENTIFIER_1, 10.00, 15.00, "01", "01", "01");
private final Preis p2 = new Preis(ARTICLE_IDENTIFIER_2, 20.00, 25.00, "02", "02", "02");
@Test
void testGetPreisReturnsOneCorrectPreis() {
when(this.sapCarService.getPreise(Arrays.asList(ARTICLE_IDENTIFIER_1), TEST_STORE_IDENTIFIER, true)).thenReturn(Arrays.asList(this.p1));
final List<Preis> actual = this.preisService.getPreis(ARTICLE_IDENTIFIER_1, TEST_STORE_IDENTIFIER);
verify(this.sapCarService, times(1)).getPreise(anyList(), anyString(), anyBoolean());
assertNotNull(actual);
assertEquals(1, actual.size());
assertEquals(this.p1, actual);
}
}
Моя реализация:
private Preis searchPreisInCache(String key) {
final Element preisOptional = this.cacheManager.getCache("preis").get(key); // NPE here
if (preisOptional != null) {
final Preis preis = (Preis) preisOptional.getObjectValue();
logger.info(String.format("Preis with key '%s' found in cache 'preis'.", key));
return preis;
}
return null;
}
Отслеживание стека показало, что NPE попадает внутрь net.sf.ehcache.Cache
класса:
public final Element get(Object key) throws IllegalStateException, CacheException {
getObserver.begin(); // NPE thrown here
checkStatus();
if (disabled) {
getObserver.end(GetOutcome.MISS_NOT_FOUND);
return null;
}
Element element = compoundStore.get(key);
if (element == null) {
getObserver.end(GetOutcome.MISS_NOT_FOUND);
return null;
} else if (isExpired(element)) {
tryRemoveImmediately(key, true);
getObserver.end(GetOutcome.MISS_EXPIRED);
return null;
} else if (!skipUpdateAccessStatistics(element)) {
element.updateAccessStatistics();
}
getObserver.end(GetOutcome.HIT);
return element;
}
Есть ли какое-либо простое решение для этой проблемы, если я просто хочу, чтобы Cache
возвращалось null
при каждом его вызове?
Комментарии:
1. Получение NPE в
Cache
является признаком того, что ваш тестируемый класс не использует макет, поскольку выполняется его фактическая реализация, что, в свою очередь, указывает на то, чтоCacheManager
экземпляр также не является макетом. Как вы получаете экземплярCacheManager
в своей реализации. Кроме того, не могли бы вы, пожалуйста, опубликовать свой полный тест, включая импорт и аннотации классов (если таковые имеются)?2. Я получаю это с помощью
@Autowired
. У меня также есть простойehcache.xml
файл конфигурации. Я также отредактирую свой вопрос, чтобы включить мой полный тестовый класс.3. Я просто немного отладил и увидел, что экземпляр
cache
имеет типCache$MockitoMock
иcacheManager
являетсяCacheManager$MockitoMock
. Так что они действительно издеваются.4. Хороший улов! Это может быть причиной. Традиционно Mockito 1.x не мог имитировать конечный метод / классы, и для этих задач приходилось прибегать к PowerMockito . В версии v2.x теперь это возможно , поэтому у вас есть несколько вариантов в зависимости от вашей версии
5. Нет проблем. В сложившихся обстоятельствах я считаю, что слава полностью ваша. Не стесняйтесь публиковать ответ со своими выводами и решением и выбирать его как правильный.
Ответ №1:
Mockito
невозможно издеваться над final
методами и классами без некоторой настройки. Как указал Морфик, это возможно с Mockito
версией v2.x, как объяснено здесь и здесь.
По сути, вам нужно добавить текстовый файл с именем org.mockito.plugins.MockMaker
в каталог src/test/resources/mockito-extensions
с содержимым mock-maker-inline
и tada, Mockito
который может имитировать конечные методы и классы.
Однако здесь используется другой движок с другими ограничениями, так что имейте это в виду.