Как убирать издевательства в весенних тестах при использовании 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 ответов
О размещении сброса после теста методом
Я думаю, что сброс макетов лучше выполнять после метода тестирования, поскольку это подразумевает, что в ходе теста действительно произошло что-то, что необходимо очистить.
Если бы сброс был выполнен перед тестовым методом, я бы чувствовал себя неуверенно, что, черт возьми, произошло перед тестом, который должен быть сброшен? А как насчет объекта не mocks? Есть ли причина (может быть, есть) для этого? Если есть причина, почему она не упоминается в коде (например, имя метода)? И так далее.
Не фанат весенних тестов
Фон
Использование Spring похоже на отказ от модульного тестирования класса; с Spring у вас меньше контроля над тестом: изоляция, создание экземпляров, жизненный цикл, чтобы сослаться на некоторые из свойств look в модульном тесте. Однако во многих случаях Spring предлагает библиотеки и фреймворки, которые не настолько "прозрачны", для тестирования вам лучше протестировать реальное поведение всего материала, как, например, в Spring MVC, Spring Batch и т. Д.
И создание этих тестов намного сложнее, так как во многих случаях разработчик вынужден создавать интеграционные тесты для серьезного тестирования поведения производственного кода. Так как многие разработчики не знают всех подробностей о том, как ваш код живет внутри Spring, это может привести к множеству сюрпризов, чтобы попытаться протестировать класс с помощью модульных тестов.
Но проблемы продолжаются, тесты должны быть быстрыми и небольшими, чтобы обеспечить быструю обратную связь с разработчиками (плагин IDE, такой как Infinitest, отлично подходит для этого), но тесты с Spring по своей природе более медленные и занимают больше памяти. Который имеет тенденцию запускать их реже и даже полностью избегать их на локальной рабочей станции... чтобы потом обнаружить на сервере CI, что они выходят из строя.
Жизненный цикл с Мокито и Весной
Таким образом, когда интеграционный тест создается для подсистемы, вы получаете множество объектов и, очевидно, коллабораторов, которые, вероятно, подвергаются насмешкам. Жизненный цикл контролируется Spring Runner, а Mockito - нет. Таким образом, вы сами должны управлять жизненным циклом издевательств.
Снова о жизненном цикле во время проекта с Spring Batch у нас были некоторые проблемы с остаточными эффектами на немодалы, поэтому у нас было два варианта: сделать только один метод тестирования для каждого класса тестирования или использовать грязный контекстный трюк:
@DirtiesContext(classMode=ClassMode.AFTER_EACH_TEST_METHOD)
, Это привело к более медленным тестам, большему потреблению памяти, но это был лучший вариант, который у нас был. С этим трюком вам не нужно будет сбрасывать mockito mocks.
Возможный свет в темноте
Я недостаточно хорошо знаю проект, но 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 в качестве альтернативы
@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 в тестах.