对于 mocks 初始化,使用 runner 或 MockitoAnnotations.initMocks
是严格等效的解决方案。从 MockitoJUnitRunner 的 javadoc :
JUnit 4.5 runner 初始化使用 Mock 注释的模拟,因此不需要显式使用 MockitoAnnotations.initMocks(Object)。在每个测试方法之前初始化模拟。
当您已经在测试用例上配置了特定的运行器(例如 SpringJUnit4ClassRunner
)时,可以使用第一个解决方案(使用 MockitoAnnotations.initMocks
)。
第二个解决方案(使用 MockitoJUnitRunner
)是更经典的,也是我最喜欢的。代码更简单。使用跑步者提供了 automatic validation of framework usage 的巨大优势(由 this answer 中的 @David Wallace 描述)。
两种解决方案都允许在测试方法之间共享模拟(和间谍)。再加上 @InjectMocks
,它们允许非常快速地编写单元测试。样板模拟代码减少了,测试更容易阅读。例如:
@RunWith(MockitoJUnitRunner.class)
public class ArticleManagerTest {
@Mock private ArticleCalculator calculator;
@Mock(name = "database") private ArticleDatabase dbMock;
@Spy private UserProvider userProvider = new ConsumerUserProvider();
@InjectMocks private ArticleManager manager;
@Test public void shouldDoSomething() {
manager.initiateArticle();
verify(database).addListener(any(ArticleListener.class));
}
@Test public void shouldDoSomethingElse() {
manager.finishArticle();
verify(database).removeListener(any(ArticleListener.class));
}
}
优点:代码很少
缺点:黑魔法。 IMO 这主要是由于 @InjectMocks 注释。使用此注释“您可以摆脱代码的痛苦”(请参阅 @Brice 的精彩评论)
第三种解决方案是在每个测试方法上创建你的模拟。正如 @mlk 在其答案中所解释的那样,它允许具有“独立测试”。
public class ArticleManagerTest {
@Test public void shouldDoSomething() {
// given
ArticleCalculator calculator = mock(ArticleCalculator.class);
ArticleDatabase database = mock(ArticleDatabase.class);
UserProvider userProvider = spy(new ConsumerUserProvider());
ArticleManager manager = new ArticleManager(calculator,
userProvider,
database);
// when
manager.initiateArticle();
// then
verify(database).addListener(any(ArticleListener.class));
}
@Test public void shouldDoSomethingElse() {
// given
ArticleCalculator calculator = mock(ArticleCalculator.class);
ArticleDatabase database = mock(ArticleDatabase.class);
UserProvider userProvider = spy(new ConsumerUserProvider());
ArticleManager manager = new ArticleManager(calculator,
userProvider,
database);
// when
manager.finishArticle();
// then
verify(database).removeListener(any(ArticleListener.class));
}
}
优点:你清楚地展示了你的 api 是如何工作的(BDD ......)
缺点:有更多样板代码。 (模拟创作)
我的建议是妥协。将 @Mock
注释与 @RunWith(MockitoJUnitRunner.class)
一起使用,但不要使用 @InjectMocks
:
@RunWith(MockitoJUnitRunner.class)
public class ArticleManagerTest {
@Mock private ArticleCalculator calculator;
@Mock private ArticleDatabase database;
@Spy private UserProvider userProvider = new ConsumerUserProvider();
@Test public void shouldDoSomething() {
// given
ArticleManager manager = new ArticleManager(calculator,
userProvider,
database);
// when
manager.initiateArticle();
// then
verify(database).addListener(any(ArticleListener.class));
}
@Test public void shouldDoSomethingElse() {
// given
ArticleManager manager = new ArticleManager(calculator,
userProvider,
database);
// when
manager.finishArticle();
// then
verify(database).removeListener(any(ArticleListener.class));
}
}
优点:您清楚地展示了您的 api 是如何工作的(我的 ArticleManager
是如何实例化的)。没有样板代码。
缺点:测试不是独立的,减少代码的痛苦
现在(从 v1.10.7 开始)有第四种方法来实例化模拟,它使用称为 MockitoRule 的 JUnit4 规则。
@RunWith(JUnit4.class) // or a different runner of your choice
public class YourTest
@Rule public MockitoRule rule = MockitoJUnit.rule();
@Mock public YourMock yourMock;
@Test public void yourTestMethod() { /* ... */ }
}
JUnit 查找 subclasses of TestRule annotated with @Rule,并使用它们包装 Runner 提供的测试语句。这样做的结果是,您可以将 @Before 方法、@After 方法,甚至 try...catch 包装器提取到规则中。您甚至可以在测试中与这些交互,就像 ExpectedException 所做的那样。
MockitoRule 的行为几乎与 MockitoJUnitRunner 完全一样,除了您可以使用任何其他运行程序,例如 Parameterized(它允许您的测试构造函数接受参数,以便您的测试可以运行多次),或者 Robolectric 的测试runner(因此它的类加载器可以为 Android 原生类提供 Java 替换)。这使得它在最近的 JUnit 和 Mockito 版本中使用起来更加灵活。
总之:
Mockito.mock():直接调用,不支持注释或使用验证。
MockitoAnnotations.initMocks(this):注解支持,无使用验证。
MockitoJUnitRunner:注解支持和使用验证,但你必须使用那个运行器。
MockitoRule:任何 JUnit 运行器的注释支持和使用验证。
@get:Rule val mockitoRule: MockitoRule = MockitoJUnit.rule()
JUnit 5 Jupiter 的一个小例子,“RunWith”已被删除,您现在需要使用“@ExtendWith”注释来使用扩展。
@ExtendWith(MockitoExtension.class)
class FooTest {
@InjectMocks
ClassUnderTest test = new ClassUnderTest();
@Spy
SomeInject bla = new SomeInject();
}
1. 使用 MockitoAnnotations.openMocks():
Mockito 2 中的 MockitoAnnotations.initMock()
方法已弃用,并在 Mockito 3 中替换为 MockitoAnnotations.openMocks()
。MockitoAnnotations.openMocks()
方法返回一个 AutoClosable
实例,可用于在测试后关闭资源。下面是使用 MockitoAnnotations.openMocks()
的示例。
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
class MyTestClass {
AutoCloseable openMocks;
@BeforeEach
void setUp() {
openMocks = MockitoAnnotations.openMocks(this);
// my setup code...
}
@Test
void myTest() {
// my test code...
}
@AfterEach
void tearDown() throws Exception {
// my tear down code...
openMocks.close();
}
}
2. 使用@ExtendWith(MockitoExtension.class):
从 JUnit5 开始,@RunWith
已被删除。以下是使用 @ExtendWith
的示例:
@ExtendWith(MockitoExtension.class)
class MyTestClass {
@BeforeEach
void setUp() {
// my setup code...
}
@Test
void myTest() {
// my test code...
}
@AfterEach
void tearDown() throws Exception {
// my tear down code...
}
}
MockitoAnnotations.initMocks(this);
更新到示例 1 效果很好。大多数其他答案现在已经过时了。
MockitoAnnotations 和 runner 已经在上面进行了很好的讨论,所以我要为不受欢迎的人投入我的 tuppence:
XXX mockedXxx = mock(XXX.class);
我使用它是因为我发现它更具描述性,并且我更喜欢(不是完全禁止)单元测试不使用成员变量,因为我喜欢我的测试(尽可能地)自包含。
有一种巧妙的方法可以做到这一点。
如果是单元测试,你可以这样做:@RunWith(MockitoJUnitRunner.class) public class MyUnitTest { @Mock private MyFirstMock myFirstMock; @Mock private MySecondMock mySecondMock; @Spy private MySpiedClass mySpiedClass = new MySpiedClass(); // 它将向该对象注入 2 个模拟和每个反射的间谍对象 // @InjectMocks 的 java 文档很好地解释了它如何以及何时进行注入 @InjectMocks private MyClassToTest myClassToTest; @Test public void testSomething() { } }
编辑:如果它是一个集成测试,你可以这样做(不打算与 Spring 一起使用。只是展示你可以用不同的 Runners 初始化模拟):@RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration("aplicationContext.xml")公共类 MyIntegrationTest { @Mock private MyFirstMock myFirstMock; @Mock private MySecondMock mySecondMock; @Spy private MySpiedClass mySpiedClass = new MySpiedClass(); // 它将向该对象注入 2 个模拟和每个反射的间谍对象 // @InjectMocks 的 java 文档很好地解释了它如何以及何时进行注入 @InjectMocks private MyClassToTest myClassToTest; @Before public void setUp() throws Exception { MockitoAnnotations.initMocks(this); } @Test public void testSomething() { } }
在 Mockito
的最新版本中,方法 MockitoAnnotations.initMocks
已弃用
首选方式是使用
JUnit4 的 MockitoJUnitRunner 或 MockitoRule
JUnit5 的 MockitoExtension
TestNG 的 MockitoTestNGListener
如果您不能使用专用的跑步者/扩展程序,您可以使用 MockitoSession
如果您需要/需要,其他答案很棒,并且包含更多详细信息。除此之外,我想添加一个 TL;DR:
更喜欢使用@RunWith(MockitoJUnitRunner.class) 如果你不能(因为你已经使用了不同的跑步者),更喜欢使用@Rule public MockitoRule rule = MockitoJUnit.rule();类似于(2),但你不应该再使用它了:@Before public void initMocks() { MockitoAnnotations.initMocks(this);如果您只想在其中一个测试中使用模拟并且不想将其暴露给同一测试类中的其他测试,请使用 X x = mock(X.class)
(1) 和 (2) 和 (3) 是互斥的。 (4) 可与其他组合使用。
不定期副业成功案例分享
MockitoJUnitRunner
的唯一优势是不正确的。有关差异的更多信息,请参阅 stackoverflow.com/questions/10806345/… 中的问题和我对此的回答。Collaborator collab = mock(Collaborator.class)
,我认为这种方式肯定是一种有效的方法。虽然这可能会很冗长,但您可以获得测试的可理解性和可重构性。两种方式各有利弊,我还没有决定哪种方法更好。 Amyway 总是有可能写废话,并且可能取决于上下文和编码器。