Подтвердить что ты не робот

Как очистить mocks в тестах spring при использовании Mockito

Я новичок в Mockito и испытываю некоторые проблемы с очисткой.

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

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

Я заметил, что многие статьи рекомендуют использовать Mockito в тестах spring, и я хотел бы попробовать. Он работает хорошо, пока я не напишу два тестовых метода в тестовом примере. Каждый тестовый метод передавался, когда он работал один, один из них не удался, если они работали вместе. Я предположил, что это связано с тем, что макетная информация была сохранена в макете (потому что я не вижу какого-либо контекстного объекта, подобного этому в 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(...);

}
4b9b3361

Ответ 1

  • О размещении reset после тестового метода

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

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

  • Не является поклонником тестов Spring

    • Фон

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

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

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

    • Жизненный цикл с помощью Mockito и Spring

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

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

  • Возможный свет в темноте

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

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

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


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

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

@After public void reset_mocks() {
    Mockito.reset(placeOrderService);
}

Ответ 2

Тест на

Spring трудно сделать быстрым и независимым (как @Brice написал). Вот утилита litle для reset всех mocks (вы должны называть ее вручную в каждом методе @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);
            }
        }
    }
}

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

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

mockito - 1.10.19 spring -test 3.2.2.RELEASE

Ответ 3

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

@MockBean
private PlaceOrderService placeOrderService;

Ответ 4

Вместо того, чтобы вводить объект 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.