我对Mockito很新,并且在清理方面遇到了一些麻烦。
我曾经使用JMock2进行单元测试。据我所知,JMock2在上下文中保留了期望和其他模拟信息,这些信息将针对每种测试方法进行重建。因此,每种测试方法都不会受到其他测试方法的干扰。
在使用JMock2时,我采用了相同的弹簧测试策略,我发现我在post中使用的策略存在潜在问题:应用程序上下文是针对每个测试方法重建的,因此减慢了整个测试过程。
我注意到许多文章建议在春季测试中使用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(...);
}
答案 0 :(得分:38)
关于在测试方法之后放置重置
我认为在测试方法之后应该更好地重置模拟,因为它意味着测试期间确实发生了需要清理的事情。
如果在测试方法之前完成重置,我会感到不确定,在应该重置的测试之前发生了什么?关于非模拟对象怎么样?有没有理由(也许有)呢?如果有一个原因导致代码中没有提到它(例如方法名称)?等等。
不喜欢基于Spring的测试
背景
使用Spring就像放弃单元测试课程一样;使用Spring,您可以减少对测试的控制:隔离,实例化,生命周期,以引用单元测试中的一些外观属性。但是在很多情况下,Spring提供的库和框架并不是“ transparent ”,用于测试你更好地测试整个东西的实际行为,比如Spring MVC,Spring Batch等。
制作这些测试要麻烦得多,因为在许多情况下,开发人员强制要求集成测试来认真测试生产代码的行为。由于许多开发人员并不了解代码如何在Spring中运行的每一个细节,这可能会导致许多意外尝试使用单元测试来测试类。
但问题还在继续,测试应该快速而小,以便为开发人员提供快速反馈(像Infinitest这样的IDE插件非常适合),但使用Spring的测试本质上更多缓慢和更多的内存消耗。这往往不太经常运行它们甚至完全避免它们在本地工作站上...以后在CI服务器上发现它们失败。
Mockito和Spring的生命周期
因此,当为子系统制作集成测试时,最终会出现许多对象,显然是协作者,可能会被嘲笑。生命周期由Spring Runner控制,但Mockito模拟不是。所以你必须自己管理模拟生命周期。
关于使用Spring Batch的项目期间的生命周期,我们有一些关于非模拟的残留效果的问题,所以我们有两个选择,每个测试类只使用一个测试方法或使用dirty context技巧:{{1} }。这会导致测试速度变慢,内存消耗增加,但这是我们的最佳选择。通过这个技巧,你不必重置Mockito模拟。
黑暗中的可能光线
我对这个项目不太了解,但是springockito可以为你提供一些生命周期的糖。 annotation subproject似乎更好:它似乎让Spring管理Spring容器中bean的生命周期,并让测试控制如何使用模拟。我仍然没有这个工具的经验,所以可能会有惊喜。
作为一个免责声明,我非常喜欢Spring,它提供了许多非凡的工具来简化其他框架的使用,它可以提高生产力,它有助于设计,但是像人类发明的每一个工具一样,总是有一个粗略的边缘(如果不是更多) ...)。
另一方面,有趣的是,这个问题碰巧发生在 JUnit 上下文中,因为JUnit实例化了每个测试方法的测试类。如果测试基于 TestNG ,那么方法可能会有所不同,因为TestNG只创建测试类的一个实例,无论使用Spring,静态模拟字段都是必需的。
旧答案:
我不喜欢在春天的情结中使用Mockito嘲笑。但你能找到类似的东西吗?
@DirtiesContext(classMode=ClassMode.AFTER_EACH_TEST_METHOD)
答案 1 :(得分:8)
Spring Boot has @MockBean
annotation,您可以使用它来模拟您的服务。您不需要再手动重置模拟。只需将@Autowired
替换为@MockBean
:
@MockBean
private PlaceOrderService placeOrderService;
答案 2 :(得分:4)
基于Spring的测试难以快速且独立(如@Brice wrote)。这是一个用于重置所有模拟的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的模拟始终由spring包装到代理对象中,因此要重置或验证此模拟,您应首先解开它。否则,您将获得UnfinishedVerificationException
,这可能会在不同的测试中不时出现。
就我而言AopUtils.isAopProxy
就够了。但如果您遇到代理问题,还有AopUtils.isCglibProxy
和AopUtils.isJdkDynamicProxy
。
mockito是1.10.19
弹簧测试是3.2.2.RELEASE
答案 3 :(得分:3)
您可能应该让Mockito在每次测试之前将其初始化为placeOrderService
,而不是注入@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
个对象的测试用例类扩展它。
答案 4 :(得分:1)
另一种在 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 结合起来应该是清理/重置模拟的好方法。
答案 5 :(得分:0)
您确实可以使用 @MockBean
(as previously answered)。它确实在该上下文中的每个测试之后重置模拟但它也可能为其他不使用完全相同的 @MockBean
/{{ 组合的测试类重新构建整个 spring 上下文1}},这可能会导致构建测试阶段缓慢,因为需要启动许多上下文!
如果您使用的是 spring boot 2.2+,则可以使用 @MockInBean 作为 @SpyBean
的替代。
它将重置您的模拟并保持您的 Spring 上下文干净(并快速测试)。
@MockBean
免责声明:我为此目的创建了这个库:清理模拟并避免在测试中重新创建 Spring 上下文常量。