Как убрать моки в весенних тестах при использовании Mockito

Я новичок в Mockito, и у меня есть проблемы с очисткой.

Раньше я использовал JMock2 для модульных тестов. Насколько мне известно, JMock2 сохраняет ожидания и другую фиктивную информацию в контексте, который будет перестраиваться для каждого метода тестирования. Таким образом, ни один метод испытаний не взаимодействует с другими.

Я принял ту же стратегию для весенних тестов при использовании JMock2, я обнаружил потенциальную проблему со стратегиями, которые я использовал в моем post: контекст приложения перестраивается для каждого метода тестирования, что замедляет всю процедуру тестирования.

Я заметил, что многие статьи рекомендуют использовать Mockito в весенних тестах, и я хотел бы попробовать. Это работает хорошо, пока я не напишу два тестовых метода в тестовом примере. Каждый тестовый метод прошел успешно, если он выполнялся по отдельности. Один из них не прошел, если они выполнялись вместе. Я предположил, что это связано с тем, что фиктивная информация была сохранена в самом макете (потому что я не вижу никакого объекта контекста, подобного этому в JMock), а макет (и контекст приложения) используется совместно в обоих методах тестирования.

Я решил это, добавив reset() в метод @Before. Мой вопрос заключается в том, как лучше всего справиться с этой ситуацией (в javadoc reset() говорится, что код пахнет, если вам нужен reset())? Любая идея приветствуется, заранее спасибо.

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {
    "file:src/main/webapp/WEB-INF/booking-servlet.xml",
    "classpath:test-booking-servlet.xml" })
@WebAppConfiguration
public class PlaceOrderControllerIntegrationTests implements IntegrationTests {

@Autowired
private WebApplicationContext wac;

private MockMvc mockMvc;

@Autowired
private PlaceOrderService placeOrderService;

@Before
public void setup() {
    this.mockMvc = webAppContextSetup(this.wac).build();

    reset(placeOrderService);// reset mock
}

@Test
public void fowardsToFoodSelectionViewAfterPendingOrderIsPlaced()
        throws Exception {

    final Address deliveryAddress = new AddressFixture().build();
    final String deliveryTime = twoHoursLater();
    final PendingOrder pendingOrder = new PendingOrderFixture()
            .with(deliveryAddress).at(with(deliveryTime)).build();

    when(placeOrderService.placeOrder(deliveryAddress, with(deliveryTime)))
            .thenReturn(pendingOrder);

    mockMvc.perform(...);

}

@Test
public void returnsToPlaceOrderViewWhenFailsToPlaceOrder() throws Exception {

    final Address deliveryAddress = new AddressFixture().build();
    final String deliveryTime = twoHoursLater();
    final PendingOrder pendingOrder = new PendingOrderFixture()
            .with(deliveryAddress).at(with(deliveryTime)).build();

    NoAvailableRestaurantException noAvailableRestaurantException = new NoAvailableRestaurantException(
            deliveryAddress, with(deliveryTime));
    when(placeOrderService.placeOrder(deliveryAddress, with(deliveryTime)))
            .thenThrow(noAvailableRestaurantException);

            mockMvc.perform(...);

}

person Yugang Zhou    schedule 10.08.2013    source источник


Ответы (6)


  1. О размещении сброса после метода тестирования

    Я думаю, что сброс макетов лучше выполнять после метода тестирования, так как это подразумевает, что во время теста действительно произошло что-то, что необходимо очистить.

    Если сброс выполняется до метода тестирования, я не уверен, что, черт возьми, произошло перед тестом, который следует сбросить? А как насчет объекта, не являющегося макетом? Есть ли причина (а может и есть) для этого? Если есть причина, по которой это не упоминается в коде (например, имя метода)? И так далее.

  2. Не являюсь поклонником тестов на основе Spring

    1. Задний план

      Использование Spring похоже на отказ от модульного тестирования класса; с Spring у вас меньше контроля над тестом: изоляция, создание экземпляра, жизненный цикл, чтобы процитировать некоторые из просматриваемых свойств в модульном тесте. Однако во многих случаях Spring предлагает библиотеки и фреймворки, которые не настолько "прозрачны", для тестирования вам лучше протестировать фактическое поведение всего материала, например, с Spring MVC, Spring Batch и т. д.

      И создание этих тестов гораздо более проблематично, поскольку во многих случаях разработчику приходится создавать интеграционные тесты, чтобы серьезно проверить поведение производственного кода. Поскольку многие разработчики не знают всех подробностей о том, как ваш код живет внутри Spring, это может привести к множеству сюрпризов при попытке протестировать класс с помощью модульных тестов.

      Но проблемы продолжаются, тесты должны быть быстрыми и небольшими, чтобы дать разработчикам быструю обратную связь (подключаемый модуль IDE, такой как Infinitest отлично подходит для этого), но тесты с Spring по своей сути более медленные и потребляют больше памяти. Который имеет тенденцию запускать их реже и даже полностью избегать их на локальной рабочей станции ... чтобы позже обнаружить на сервере CI, что они терпят неудачу.

    2. Жизненный цикл с Mockito и Spring

      Таким образом, когда для подсистемы создается интеграционный тест, вы получаете множество объектов и, очевидно, соавторов, над которыми, вероятно, издеваются. Жизненный цикл контролируется Spring Runner, а макеты Mockito — нет. Таким образом, вы должны сами управлять жизненным циклом макетов.

      Еще раз о жизненном цикле во время проекта с Spring Batch у нас были некоторые проблемы с остаточными эффектами для не-моков, поэтому у нас было два варианта: сделать только один тестовый метод для каждого тестового класса или использовать грязный контекст трюк: @DirtiesContext(classMode=ClassMode.AFTER_EACH_TEST_METHOD). Это приводило к более медленным тестам, большему потреблению памяти, но это был лучший вариант, который у нас был. С помощью этого трюка вам не придется сбрасывать моки Mockito.

  3. Возможный свет во тьме

    Я недостаточно хорошо знаком с проектом, но springockito может рассказать вам немного о жизненном цикле. подпроект аннотаций кажется еще лучше: он позволяет Spring управлять жизненным циклом bean-компонентов. в контейнере Spring и позволить тесту контролировать использование макетов. Тем не менее, у меня нет опыта работы с этим инструментом, поэтому могут быть сюрпризы.

В качестве отказа от ответственности: мне очень нравится Spring, он предлагает множество замечательных инструментов для упрощения использования других фреймворков, он может повысить производительность, он помогает дизайну, но, как и любой инструмент, изобретенный людьми, всегда есть грубая грань (если не более... ).

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


Старый ответ:

Я не большой поклонник использования моков Mockito в весеннем контексте. Но не могли бы вы искать что-то вроде:

@After public void reset_mocks() {
    Mockito.reset(placeOrderService);
}
person Brice    schedule 12.08.2013
comment
Спасибо за обновление. В целом я с вами согласен. Но ограниченное использование весенних тестов по-прежнему полезно в некоторых ситуациях, особенно когда вы хотите протестировать свою инфраструктуру, и они отделены от других частей системы. Но у меня есть желание использовать весенние тесты для контроллеров mvc (вместо этого я мог бы использовать автономный mvc). Моки нужны, если я это делаю, но если я этого не делаю, конфигурации не рассматриваются, и они играют важную роль в mvc. - person Yugang Zhou; 13.08.2013
comment
Эй, я не говорил, что никогда не писал тесты с помощью Spring;) Если вы хотите серьезно использовать Spring Batch, я бы даже рекомендовал его, но следует использовать правильные данные! Теперь, как вы упомянули, вы ищете тесты инфраструктуры, а это совсем другая история. Я не уверен, что в этих тестах много хорошей или плохой практики с моками. Возможно, эти тесты должны подтвердить некоторые способности система. На мой взгляд, жизненный цикл макетов - это деталь, если она правильная и понятная. Также хотя они должны помочь только для изоляции. - person Brice; 13.08.2013
comment
Извините за поздний ответ. Я попробовал springockito в эти выходные, но, похоже, не удалось загрузить WebApplicationContext. Поэтому для меня использование reset(mock) является наиболее удобным решением (я пересмотрел сброс на метод @ After, когда требовалось больше макетов). Спасибо. - person Yugang Zhou; 18.08.2013
comment
Пожалуйста. Кроме того, со временем в проекте появится хорошая практика, некоторые из которых специфичны для проекта, некоторые более общие. Например, я обычно использую несколько методов @After/@Before для выделенных задач (с соответствующими именами); и если требуется порядок, то только один метод установки/разборки, чем вызов нескольких методов. Ваше здоровье. - person Brice; 18.08.2013
comment
Я не согласен с 1) such as Infinitest are great for that), but tests with Spring are inherently more slow and more memory consuming просто попробуйте gradle: gradle -t test он пересоберет/запустит только необходимые классы/тесты 2) @DirtiesContext - используйте это, только если это необходимо - person Maksim Kostromin; 13.05.2017

В Spring Boot есть @MockBean аннотацию, которую можно использовать для имитации своего сервиса. Вам больше не нужно сбрасывать макеты вручную. Просто замените @Autowired на @MockBean:

@MockBean
private PlaceOrderService placeOrderService;
person Lu55    schedule 11.09.2017
comment
Это лучший вариант для имитации объектов в весеннем тесте. Я с трудом могу вспомнить пример, где mockBean не подходит для теста. Поэтому больше не нужно беспокоиться о сбросе макетов - person voipp; 29.11.2018
comment
Использование @MockBean может привести к замедлению этапов тестирования сборки, поскольку может воссоздать весь испорченный им контекст Spring. См. stackoverflow.com/questions/45587213/ или проблема с @MockBean. Вы можете использовать @MockInBean в качестве альтернативы @MockBean, которая не сбрасывает контекст. См. мой ответ. - person Antoine Meyer; 18.03.2021

Вместо того, чтобы вводить объект placeOrderService, вам, вероятно, следует просто позволить Mockito инициализировать его как @Mock перед каждым тестом, например, так:

@Mock private PlaceOrderService placeOrderService;

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

Как рекомендовано в Javadoc здесь: http://docs.mockito.googlecode.com/hg/latest/org/mockito/MockitoAnnotations.html

Вы даже можете поместить метод @Before в суперкласс и просто расширить его для каждого класса тестового примера, который использует объекты @Mock.

person superEb    schedule 10.08.2013
comment
Спасибо за ответ. Но этот код приводит к NoSuchBeanException: не определен bean-компонент с именем «placeOrderService». Как я могу ввести placeOrderservice в контроллер? - person Yugang Zhou; 11.08.2013
comment
Что именно вы пытаетесь проверить? Если вы просто тестируете логику контроллера, то я думаю, что вы захотите создать его самостоятельно и смоделировать все его зависимости, для чего не требуется ApplicationContext. В противном случае, если вы тестируете интеграцию компонентов, вам не обязательно понадобятся макеты. Но если вы хотите использовать макеты в интеграционном тесте, просто перезапишите службу, внедренную в контроллер Spring, с помощью @Mock. - person superEb; 11.08.2013

Тест на основе Spring сложно сделать быстрым и независимым (как писал @Brice ). Вот небольшой служебный метод для сброса всех моков (вы должны вызывать его вручную в каждом методе @Before):

import org.mockito.Mockito;
import org.springframework.aop.framework.Advised;
import org.springframework.aop.support.AopUtils;
import org.springframework.context.ApplicationContext;


public class MyTest {
    public void resetAll(ApplicationContext applicationContext) throws Exception {
        for (String name : applicationContext.getBeanDefinitionNames()) {
            Object bean = applicationContext.getBean(name);
            if (AopUtils.isAopProxy(bean) && bean instanceof Advised) {
                bean = ((Advised)bean).getTargetSource().getTarget();
            }
            if (Mockito.mockingDetails(bean).isMock()) {
                Mockito.reset(bean);
            }
        }
    }
}

Как вы видите, есть итерация для всех bean-компонентов, проверьте, является ли bean-компонент макетом или нет, и сбросьте макет. Особое внимание обращаю на вызов AopUtils.isAopProxy и ((Advised)bean).getTargetSource().getTarget(). Если ваш bean-компонент содержит аннотацию @Transactional, макет этого bean-компонента всегда оборачивается Spring в прокси-объект, поэтому для сброса или проверки этого макета вы должны сначала развернуть его. В противном случае вы получите UnfinishedVerificationException, который время от времени может возникать в разных тестах.

В моем случае AopUtils.isAopProxy достаточно. Но есть еще AopUtils.isCglibProxy и AopUtils.isJdkDynamicProxy, если у вас проблемы с проксированием.

mockito — это 1.10.19 весенний тест — это 3.2.2.RELEASE

person Cherry    schedule 10.03.2016

Еще один способ сброса макетов в контексте весны.

https://github.com/Eedanna/mockito/issues/119#issuecomment-166823815

Предполагая, что вы используете Spring, вы можете легко реализовать это самостоятельно, получив свой ApplicationContext, а затем выполнив следующие действия:

public static void resetMocks(ApplicationContext context) {
    for ( String name : context.getBeanDefinitionNames() ) {
        Object bean = context.getBean( name );
        if (new MockUtil().isMock( bean )) {
            Mockito.reset( bean );
        }
    }
}

https://github.com/Eedanna/mockito/issues/119

объединение приведенного выше кода с @AfterAll должно быть хорошим способом очистки/сброса макетов.

person anugrahsinghal    schedule 06.04.2021

Вы действительно можете использовать @MockBean (как ответили ранее). Он сбрасывает макеты после каждого теста в этом контексте, НО он также может перестраивать весь контекст Spring для других тестовых классов, которые не используют ту же точную комбинацию @MockBean/@SpyBean, что может привести к медленные этапы тестирования сборки, так как нужно запустить много контекстов!

Если вы используете весеннюю загрузку 2.2+, вы можете использовать @MockInBean в качестве альтернативы до @MockBean. Он сбросит ваши макеты и сохранит ваш контекст Spring в чистоте (и быстро проверит).

@SpringBootTest
public class MyServiceTest {

    @MockInBean(MyService.class)
    private ServiceToMock serviceToMock;

    @Autowired
    private MyService myService;

    @Test
    public void test() {
        Mockito.when(serviceToMock.returnSomething()).thenReturn(new Object());
        myService.doSomething();
    }
}

отказ от ответственности: я создал эту библиотеку для этой цели: очистить моки и избежать повторного создания контекстной константы Spring в тестах.

person Antoine Meyer    schedule 18.03.2021