Yug*_*hou 26 spring-mvc spring-test mockito
我对Mockito很新,在清理方面遇到了一些麻烦.
我曾经使用JMock2进行单元测试.据我所知,JMock2在上下文中保留了期望和其他模拟信息,这些信息将针对每种测试方法进行重建.因此,每种测试方法都不会受到其他测试方法的干扰.
我在使用JMock2时采用了相同的弹簧测试策略,我发现我在帖子中使用的策略存在潜在问题:应用程序上下文是针对每个测试方法重建的,因此减慢了整个测试过程.
我注意到许多文章建议在春季测试中使用Mockito,我想尝试一下.它运行良好,直到我在测试用例中编写两个测试方法.每个测试方法在单独运行时通过,其中一个如果一起运行则失败.我推测这是因为模拟信息保存在模拟本身中(因为我没有在JMock中看到任何类似的上下文对象)并且模拟(和应用程序上下文)在两个测试方法中共享.
我通过在@Before方法中添加reset()来解决它.我的问题是处理这种情况的最佳做法是什么(reset()的javadoc说如果你需要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(...);
}
Bri*_*ice 43
关于在测试方法之后放置重置
我认为在测试方法之后应该更好地重置模拟,因为它意味着测试期间确实发生了需要清理的事情.
如果在测试方法之前完成重置,我会感到不确定,在应该重置的测试之前发生了什么?关于非模拟对象怎么样?有没有理由(也许有)呢?如果有一个原因导致代码中没有提到它(例如方法名称)?等等.
不是基于Spring测试的粉丝
背景
使用Spring就像放弃单元测试一个类; 使用Spring,您可以减少对测试的控制:隔离,实例化,生命周期,在单元测试中引用一些外观属性.但是在许多情况下,Spring提供的库和框架并不是" 透明的 ",用于测试您更好地测试整个内容的实际行为,例如Spring MVC,Spring Batch等.
并且制作这些测试要麻烦得多,因为它在许多情况下强制开发人员进行集成测试以认真测试生产代码的行为.由于许多开发人员并不了解您的代码如何在Spring中运行的每一个细节,这可能会导致许多意外尝试使用单元测试来测试类.
但问题仍在继续,测试应该快速而小巧,以便给开发人员提供快速反馈(IDE插件,如Infinitest非常适合),但使用Spring的测试本身就更慢,更耗费内存.这往往不太经常运行它们甚至完全避免它们在本地工作站上...以后在CI服务器上发现它们失败.
与Mockito和春天的生命周期
因此,当为子系统制作集成测试时,您最终会得到许多对象,显然是协作者,可能会被嘲笑.生命周期由Spring Runner控制,但Mockito模拟不是.所以你必须自己管理模拟生命周期.
关于使用Spring Batch的项目期间的生命周期,我们有一些关于非模拟的残留效果的问题所以我们有两个选择,每个测试类只使用一个测试方法或使用脏上下文技巧:@DirtiesContext(classMode=ClassMode.AFTER_EACH_TEST_METHOD).这会导致测试速度变慢,内存消耗增加,但这是我们的最佳选择.有了这个技巧,你就不必重置Mockito模拟了.
在黑暗中可能的光
我对这个项目不太了解,但是springockito可以为你提供一些生命周期的糖.该注释子项目似乎更好:它似乎让Spring管理bean的生命周期在Spring容器中,并让测试控制是如何被使用嘲弄.我仍然没有这个工具的经验,所以可能会有惊喜.
作为免责声明,我非常喜欢Spring,它提供了许多非凡的工具来简化其他框架的使用,它可以提高生产力,它有助于设计,但是像人类发明的每一个工具一样,总是有一个粗略的边缘(如果不是更多...... ).
另外,有趣的是,这个问题碰巧发生在JUnit上下文中,因为JUnit实例化了每个测试方法的测试类.如果测试基于TestNG,那么方法可能会有所不同,因为TestNG只创建测试类的一个实例,无论使用Spring,静态模拟字段都是强制性的.
老答案:
我不喜欢在春天的情结中使用Mockito嘲笑.但你能找到类似的东西:
@After public void reset_mocks() {
    Mockito.reset(placeOrderService);
}
Lu5*_*u55 12
Spring Boot具有@MockBean可用于模拟服务的注释.您不需要再手动重置模拟.只需替换@Autowired为@MockBean:
@MockBean
private PlaceOrderService placeOrderService;
与其注入placeOrderService对象,不如让 Mockito@Mock在每次测试之前将其初始化为 a ,方法如下:
@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对象的测试用例类扩展它。
小智 6
在 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 的测试很难快速且独立(如 @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
| 归档时间: | 
 | 
| 查看次数: | 44138 次 | 
| 最近记录: |