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

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

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

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

Я заметил, что многие статьи рекомендуют использовать 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(...);

}

6 ответов

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

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

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

  2. Не фанат весенних тестов

    1. Фон

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

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

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

    2. Жизненный цикл с Мокито и Весной

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

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

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

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

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

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


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

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

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

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

@MockBean
private PlaceOrderService placeOrderService;

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

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 ложным или нет, и сбросьте mock. Я обращаю особое внимание на вызов AopUtils.isAopProxy а также ((Advised)bean).getTargetSource().getTarget(), Если ваш бин содержит @Transactional аннотация макета этого bean-компонента всегда оборачивается пружиной в прокси-объект, поэтому для сброса или проверки этого макета вы должны сначала развернуть его. В противном случае вы получите UnfinishedVerificationException которые могут возникать в разных тестах время от времени.

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

мокито это 1.10.19весенний тест 3.2.2.RELEASE

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

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 должно быть хорошим способом очистки / сброса моков.

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

Если вы используете Spring boot 2.2+, вы можете использовать @MockInBean в качестве альтернативы . Он сбрасывает ваши макеты и сохраняет ваш контекст 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 в тестах.

Другие вопросы по тегам