如何在春季测试中使用Mockito清理mock



我曾经使用JMock2进行单元测试。据我所知,JMock2在一个上下文中保留了期望值和其他模拟信息,该上下文将为每个测试方法重新构建。因此,每种测试方法都不受其他方法的干扰。

在使用JMock2时,我对春季测试采用了相同的策略,我发现我在帖子中使用的策略存在潜在问题:为每个测试方法重建应用程序上下文,因此会减慢整个测试过程。

我注意到许多文章建议在春季测试中使用Mockito,我想试一试。它运行良好,直到我在一个测试用例中编写了两个测试方法。每个测试方法在单独运行时都通过了,其中一个方法在一起运行时失败了。我推测这是因为mock信息被保存在mock本身中(因为我在JMock中没有看到任何类似的上下文对象),并且mock(和应用程序上下文)在两个测试方法中都是共享的。

我通过在@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(...);
}
  1. 关于在测试方法后重置

我认为重新设置mock应该在测试方法之后更好地完成,因为这意味着测试过程中确实发生了一些需要清理的事情。

如果重置是在测试方法之前完成的,我会感到不确定,在应该重置的测试之前到底发生了什么?非mocks对象呢?这是有原因的吗?如果代码中没有提到它是有原因的(例如方法名称)?等等。

  1. 不喜欢基于Spring的测试

  2. 背景

    使用Spring就像放弃对类进行单元测试;使用Spring,您对测试的控制更少:隔离实例化生命周期,引用单元测试中的一些外观属性。然而,在许多情况下,Spring提供的库和框架并不是透明";,为了测试,你可以更好地测试整个东西的实际行为,比如Spring MVC、Spring Batch等。

    制作这些测试要麻烦得多,因为在许多情况下,它迫使开发人员制作集成测试来认真测试生产代码的行为。由于许多开发人员并不了解您的代码在Spring中的每一个细节,所以尝试使用单元测试来测试类可能会带来很多惊喜。

    但问题仍然存在,测试应该快速而小,以便向开发人员提供快速反馈(像Infinitest这样的IDE插件非常适合),但使用Spring的测试本质上更慢,更消耗内存。这往往会减少它们的运行频率,甚至完全避免它们在本地工作站上运行。。。以便稍后在CI服务器上发现它们失败。

  3. Mockito和Spring 的生命周期

    因此,当为一个子系统制定集成测试时,您最终会得到许多对象,显然还有合作者,这些对象可能会被嘲笑。生命周期由Spring Runner控制,但Mockito mock不是。所以你必须自己管理mocks的生命周期。

    关于使用Spring Batch的项目的生命周期,我们在非mock上遇到了一些剩余影响的问题,所以我们有两个选择,每个测试类只做一个测试方法,或者使用肮脏的上下文技巧:@DirtiesContext(classMode=ClassMode.AFTER_EACH_TEST_METHOD)。这导致测试速度变慢,内存消耗增加,但这是我们的最佳选择。有了这个技巧,你就不必重置Mockito mocks了。

  4. 黑暗中可能有亮光

我对这个项目还不够了解,但springockito可以为您提供一些关于生命周期的糖。注释子项目似乎更好:它似乎让Spring管理Spring容器中bean的生命周期,并让测试控制mock的使用方式。尽管如此,我对这个工具没有任何经验,所以可能会有惊喜。

作为免责声明,我非常喜欢Spring,它提供了许多出色的工具来简化其他框架的使用,它可以提高生产力,有助于设计,但就像人类发明的每一种工具一样,总有一个粗糙的边缘(如果不是更多的话…)。

顺便说一句,当JUnit为每个测试方法实例化测试类时,在JUnit上下文中看到这个问题是很有趣的。如果测试基于TestNG,那么方法可能会有所不同,因为TestNG只创建测试类的一个实例,无论使用Spring,静态模拟字段都是强制性的。


老答案:

我不太喜欢在春季使用Mockito mocks。但你会找这样的东西吗:

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

Spring Boot有@MockBean注释,您可以使用它来模拟您的服务。您不再需要手动重置mock。只需将@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所写)。这里有一个用于重置所有mock的litle实用程序方法(您必须在每个@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,并重置mock。我特别关注呼叫AopUtils.isAopProxy((Advised)bean).getTargetSource().getTarget()。如果您的bean包含一个@Transactional注释,那么这个bean的mock总是被spring包装到代理对象中,所以要重置或验证这个mock,您应该首先打开它。否则,你会得到一个UnfinishedVerificationException,它可能会在不同的测试中不时出现。

就我而言,AopUtils.isAopProxy就足够了。但如果代理遇到问题,也有AopUtils.isCglibProxyAopUtils.isJdkDynamicProxy

mockito是1.10.19spring测试是3.2.2.RELEASE

在spring上下文中重置mock的另一种方法。

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耦合应该是清理/重置mock的好方法。

您确实可以使用@MockBean(如前所述)。它确实会在该上下文中的每次测试后重置mock,但它也可能为不使用@MockBean/@SpyBean相同精确组合的其他测试类重新构建整个spring上下文,这可能会导致构建测试阶段缓慢,因为许多上下文需要启动!

如果您使用的是spring-boot 2.2+,则可以使用@MockInBean作为@MockBean的替代方案。它将重置mock并保持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();
    }
}

免责声明:我创建这个库的目的是:清理mock并避免在测试中重新创建Spring上下文常量

相关内容

  • 没有找到相关文章

最新更新