Mocking Logger и LoggerFactory с PowerMock и Mockito - PullRequest
51 голосов
/ 21 января 2012

У меня есть следующий Logger, который я хочу смоделировать, но для проверки вызывается запись в журнале, а не для содержимого.

private static Logger logger = 
        LoggerFactory.getLogger(GoodbyeController.class);

Я хочу Mock ЛЮБОЙ класс, который используется для LoggerFactory.getLogger() но я не мог узнать, как это сделать.Это то, чем я закончил до сих пор:

@Before
public void performBeforeEachTest() {
    PowerMockito.mockStatic(LoggerFactory.class);
    when(LoggerFactory.getLogger(GoodbyeController.class)).
        thenReturn(loggerMock);

    when(loggerMock.isDebugEnabled()).thenReturn(true);
    doNothing().when(loggerMock).error(any(String.class));

    ...
}

Я хотел бы знать:

  1. Могу ли я высмеивать статический LoggerFactory.getLogger() для работы в любом классе?
  2. Я могу только запустить when(loggerMock.isDebugEnabled()).thenReturn(true); в @Before и, таким образом, я не могу изменить характеристики для метода.Есть ли способ обойти это?

Редактировать результаты:

Я думал, что уже пробовал, и это не сработало:

 when(LoggerFactory.getLogger(any(Class.class))).thenReturn(loggerMock);

Но спасибо, как это сработало.

Однако я пробовал бесчисленное множество вариантов:

when(loggerMock.isDebugEnabled()).thenReturn(true);

Я не могу заставить loggerMock изменить свое поведение за пределами @Beforeно это происходит только с Coburtura.С Clover покрытие показывает 100%, но проблема все равно есть.

У меня есть простой класс:

public ExampleService{
    private static final Logger logger =
            LoggerFactory.getLogger(ExampleService.class);

    public String getMessage() {        
    if(logger.isDebugEnabled()){
        logger.debug("isDebugEnabled");
        logger.debug("isDebugEnabled");
    }
    return "Hello world!";
    }
    ...
}

Тогда у меня есть этот тест:

@RunWith(PowerMockRunner.class)
@PrepareForTest({LoggerFactory.class})
public class ExampleServiceTests {

    @Mock
    private Logger loggerMock;
    private ExampleServiceservice = new ExampleService();

    @Before
    public void performBeforeEachTest() {
        PowerMockito.mockStatic(LoggerFactory.class);
        when(LoggerFactory.getLogger(any(Class.class))).
            thenReturn(loggerMock);

        //PowerMockito.verifyStatic(); // fails
    }

    @Test
    public void testIsDebugEnabled_True() throws Exception {
        when(loggerMock.isDebugEnabled()).thenReturn(true);
        doNothing().when(loggerMock).debug(any(String.class));

        assertThat(service.getMessage(), is("Hello null: 0"));
        //verify(loggerMock, atLeast(1)).isDebugEnabled(); // fails
    }

    @Test
    public void testIsDebugEnabled_False() throws Exception {
        when(loggerMock.isDebugEnabled()).thenReturn(false);
        doNothing().when(loggerMock).debug(any(String.class));

        assertThat(service.getMessage(), is("Hello null: 0"));
        //verify(loggerMock, atLeast(1)).isDebugEnabled(); // fails
    }
}

В клевере я показываю 100% охват блока if(logger.isDebugEnabled()){.Но если я попытаюсь проверить loggerMock:

verify(loggerMock, atLeast(1)).isDebugEnabled();

, я получу ноль взаимодействий.Я тоже пробовал PowerMockito.verifyStatic()@Before, но также имеет нулевое взаимодействие.

Это просто странно, что Cobertura показывает, что if(logger.isDebugEnabled()){ не завершен на 100%, и Кловер делает, но оба соглашаются, что проверка не удалась.

Ответы [ 5 ]

62 голосов
/ 29 января 2012

@ Мик, попробуйте тоже подготовить владельца статического поля, например:

@PrepareForTest({GoodbyeController.class, LoggerFactory.class})

РЕДАКТИРОВАТЬ1: Я только что создал небольшой пример. Сначала контроллер:

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class Controller {
    Logger logger = LoggerFactory.getLogger(Controller.class);

    public void log() { logger.warn("yup"); }
}

Тогда тест:

import org.junit.Test;
import org.junit.runner.RunWith;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.junit4.PowerMockRunner;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyString;
import static org.mockito.Mockito.verify;
import static org.powermock.api.mockito.PowerMockito.mock;
import static org.powermock.api.mockito.PowerMockito.mockStatic;
import static org.powermock.api.mockito.PowerMockito.when;

@RunWith(PowerMockRunner.class)
@PrepareForTest({Controller.class, LoggerFactory.class})
public class ControllerTest {

    @Test
    public void name() throws Exception {
        mockStatic(LoggerFactory.class);
        Logger logger = mock(Logger.class);
        when(LoggerFactory.getLogger(any(Class.class))).thenReturn(logger);

        new Controller().log();

        verify(logger).warn(anyString());
    }
}

Обратите внимание на импорт! Примечательные библиотеки в пути к классам: Mockito, PowerMock, JUnit, logback-core, logback-clasic, slf4j


РЕДАКТИРОВАТЬ2: Поскольку это кажется популярным вопросом, я хотел бы отметить, что , если эти сообщения журнала настолько важны и требуют проверки, то есть они являются функциональной / бизнес-частью система , тогда вводящая реальную зависимость, которая проясняет, что журналы тезисов являются особенностями, будет намного лучше во всей конструкции системы , чем полагаться на статический код стандартного и технического классов регистратора.

Для этого я бы порекомендовал создать что-то вроде класса = 1019 * с такими методами, как reportIncorrectUseOfYAndZForActionX или reportProgressStartedForActionX. Это было бы полезно сделать функцию видимой для любого, кто читает код. Но это также поможет выполнить тесты, изменить детали реализации этой конкретной функции.

Следовательно, вам не понадобятся инструменты для статического моделирования, такие как PowerMock. На мой взгляд, статический код может быть хорошим, но как только тест требует проверить или имитировать статическое поведение, необходимо провести рефакторинг и ввести четкие зависимости.

14 голосов
/ 12 июня 2014

Несколько опоздал на вечеринку - я делал что-то похожее, нуждался в подсказках и оказался здесь.Не принимая во внимание - я взял весь код у Брайса, но получил «нулевое взаимодействие», чем получил Ченгиз.

Используя руководство из того, что написал Джерикс и Джозеф Люст, я думаю, я знаю, почему - я проверил свой объект как поле и обновил его в @Before, в отличие от Брайса.Тогда реальный регистратор был не фиктивным, а настоящим классом init'd, как предлагал jhriks ...

Обычно я делал бы это для моего тестируемого объекта, чтобы получить свежий объект для каждого теста.Когда я переместил поле в локальное и обновил его в тесте, все прошло нормально.Однако, если я попробовал второй тест, это был не макет в моем тесте, а макет из первого теста, и я снова получил нулевые взаимодействия.

Когда я помещаю создание макета в @BeforeClass, логгер в тестируемом объекте всегда макет, но о проблемах с этим см. Примечание ниже ...

Тестируемый класс

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class MyClassWithSomeLogging  {

    private static final Logger LOG = LoggerFactory.getLogger(MyClassWithSomeLogging.class);

    public void doStuff(boolean b) {
        if(b) {
            LOG.info("true");
        } else {
            LOG.info("false");
        }

    }
}

Тест

import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.junit4.PowerMockRunner;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import static org.mockito.Mockito.*;
import static org.powermock.api.mockito.PowerMockito.mock;
import static org.powermock.api.mockito.PowerMockito.*;
import static org.powermock.api.mockito.PowerMockito.when;


@RunWith(PowerMockRunner.class)
@PrepareForTest({LoggerFactory.class})
public class MyClassWithSomeLoggingTest {

    private static Logger mockLOG;

    @BeforeClass
    public static void setup() {
        mockStatic(LoggerFactory.class);
        mockLOG = mock(Logger.class);
        when(LoggerFactory.getLogger(any(Class.class))).thenReturn(mockLOG);
    }

    @Test
    public void testIt() {
        MyClassWithSomeLogging myClassWithSomeLogging = new MyClassWithSomeLogging();
        myClassWithSomeLogging.doStuff(true);

        verify(mockLOG, times(1)).info("true");
    }

    @Test
    public void testIt2() {
        MyClassWithSomeLogging myClassWithSomeLogging = new MyClassWithSomeLogging();
        myClassWithSomeLogging.doStuff(false);

        verify(mockLOG, times(1)).info("false");
    }

    @AfterClass
    public static void verifyStatic() {
        verify(mockLOG, times(1)).info("true");
        verify(mockLOG, times(1)).info("false");
        verify(mockLOG, times(2)).info(anyString());
    }
}

Примечание

Если у вас есть два тестас тем же ожиданием, что я должен был выполнить проверку в @AfterClass, так как вызовы статических данных складываются - verify(mockLOG, times(2)).info("true"); - вместо раз (1) в каждом тесте, так как второй тест потерпит неудачу, говоря там, где 2 вызова этого,Это красивые штаны, но я не смог найти способ очистить заклинания.Я хотел бы знать, может ли кто-нибудь придумать способ обойти это ...

5 голосов
/ 21 января 2012

В ответ на ваш первый вопрос это должно быть так же просто, как заменить:

   when(LoggerFactory.getLogger(GoodbyeController.class)).thenReturn(loggerMock);

на

   when(LoggerFactory.getLogger(any(Class.class))).thenReturn(loggerMock);

Относительно вашего второго вопроса (и, возможно, загадочного поведения с первым), Я думаю, что проблема в том, что регистратор является статическим.Таким образом,

private static Logger logger = LoggerFactory.getLogger(GoodbyeController.class);

выполняется, когда инициализируется класс , а не когда создается экземпляр объекта .Иногда это может происходить примерно в одно и то же время, поэтому вы будете в порядке, но это трудно гарантировать.Таким образом, вы настроили LoggerFactory.getLogger, чтобы он возвращал ваш макет, но переменная logger, возможно, уже была установлена ​​с реальным объектом Logger к тому времени, когда ваши макеты установлены.используя что-то вроде ReflectionTestUtils (я не знаю, работает ли это со статическими полями) или измените его со статического поля на поле экземпляра.В любом случае вам не нужно издеваться над LoggerFactory.getLogger, потому что вы будете непосредственно внедрять экземпляр ложного Logger.

2 голосов
/ 05 ноября 2014

Я думаю, что вы можете сбросить вызовы, используя Mockito.reset (mockLog). Вы должны вызывать это перед каждым тестом, так что внутри @Before будет хорошее место.

0 голосов
/ 30 ноября 2015

Используйте явную инъекцию. Ни один другой подход не позволит вам, например, параллельно выполнять тесты в одной и той же JVM.

Шаблоны, в которых используется что-либо из загрузчика классов, например связыватель статического журнала или связывание с окружающей средой, воспринимаются как logback.XML обанкротились, когда дело доходит до тестирования.

Рассмотрим параллельные тесты, о которых я упоминал, или случай, когда вы хотите перехватить запись в журнал компонента A, конструкция которого скрыта за api B. С этим последним случаем легко разобраться, если вы используете вводимый зависимостями loggerfactory сверху , но не в том случае, если вы внедрили Logger, поскольку в этой сборке нет шва в ILoggerFactory.getLogger.

И это еще не все о модульном тестировании. Иногда мы хотим, чтобы интеграционные тесты выдавали логи. Иногда мы не делаем. Кто-то хочет, чтобы некоторые журналы тестирования интеграции были выборочно подавлены, например, для ожидаемых ошибок, которые в противном случае загромождают консоль CI и приводят в замешательство. Все просто, если вы вводите ILoggerFactory из верхней части своей основной линии (или любой другой структуры, которую вы можете использовать)

Итак ...

Либо внедрите репортера в соответствии с предложением, либо выберите шаблон внедрения ILoggerFactory. С помощью явного внедрения ILoggerFactory вместо Logger вы можете поддерживать множество шаблонов доступа / перехвата и распараллеливания.

...