有很多方法可以使用 MockIto 初始化模拟对象。其中最好的方法是什么?
1.
public class SampleBaseTestCase {
@Before public void initMocks() {
MockitoAnnotations.initMocks(this);
}
@RunWith(MockitoJUnitRunner.class)
mock(XXX.class);
建议我是否有比这些更好的方法...
对于 mocks 初始化,使用 runner 或MockitoAnnotations.initMocks
是严格等效的解决方案。从MockitoJUnitRunner的 javadoc :
JUnit 4.5 runner 初始化使用 Mock 注释的模拟,因此不需要显式使用 MockitoAnnotations.initMocks(Object)。在每个测试方法之前初始化模拟。
MockitoAnnotations.initMocks
当您已经SpringJUnit4ClassRunner
在测试用例上配置了特定的运行器(例如)时,可以使用第一个解决方案(带有)。
第二种解决方案(带有MockitoJUnitRunner
)是更经典的,也是我最喜欢的。代码更简单。使用运行器提供了自动验证框架使用的巨大优势(由@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 查找使用@Rule 注释的 TestRule的子类,并使用它们来包装 Runner 提供的测试语句。这样做的结果是,您可以将 @Before 方法、@After 方法,甚至 try...catch 包装器提取到规则中。您甚至可以在测试中与这些交互,就像ExpectedException那样。
MockitoRule 的行为几乎与 MockitoJUnitRunner 完全一样,除了您可以使用任何其他运行程序,例如Parameterized(它允许您的测试构造函数接受参数,以便您的测试可以多次运行)或 Robolectric 的测试运行程序(因此它的类加载器可以提供 Java 替换对于 Android 原生类)。这使得它在最近的 JUnit 和 Mockito 版本中使用起来更加灵活。
总之:
Mockito.mock()
:直接调用,不支持注释或使用验证。MockitoAnnotations.initMocks(this)
:注释支持,没有使用验证。MockitoJUnitRunner
:注释支持和使用验证,但您必须使用该运行器。MockitoRule
: 任何 JUnit 运行器的注释支持和使用验证。另请参阅:JUnit @Rule 如何工作?
有一种巧妙的方法可以做到这一点。
如果它是一个单元测试,你可以这样做:
@RunWith(MockitoJUnitRunner.class)
public class MyUnitTest {
@Mock
private MyFirstMock myFirstMock;
@Mock
private MySecondMock mySecondMock;
@Spy
private MySpiedClass mySpiedClass = new MySpiedClass();
// It's gonna inject the 2 mocks and the spied object per reflection to this object
// The java doc of @InjectMocks explains it really well how and when it does the injection
@InjectMocks
private MyClassToTest myClassToTest;
@Test
public void testSomething() {
}
}
编辑:如果它是一个集成测试,你可以这样做(不打算与 Spring 一起使用。只是展示你可以用不同的跑步者初始化模拟):
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("aplicationContext.xml")
public class MyIntegrationTest {
@Mock
private MyFirstMock myFirstMock;
@Mock
private MySecondMock mySecondMock;
@Spy
private MySpiedClass mySpiedClass = new MySpiedClass();
// It's gonna inject the 2 mocks and the spied object per reflection to this object
// The java doc of @InjectMocks explains it really well how and when it does the injection
@InjectMocks
private MyClassToTest myClassToTest;
@Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
}
@Test
public void testSomething() {
}
}
MockitoAnnotations 和 runner 已经在上面进行了很好的讨论,所以我要为不受欢迎的人投入我的 tuppence:
XXX mockedXxx = mock(XXX.class);
我使用它是因为我发现它更具描述性,并且我更喜欢(不是完全禁止)单元测试不使用成员变量,因为我希望我的测试(尽可能地)自包含。
JUnit 5 Jupiter 的一个小例子,“RunWith”已被删除,您现在需要使用“@ExtendWith”注释来使用扩展。
@ExtendWith(MockitoExtension.class)
class FooTest {
@InjectMocks
ClassUnderTest test = new ClassUnderTest();
@Spy
SomeInject bla = new SomeInject();
}
Mockito
在该方法的最新版本中MockitoAnnotations.initMocks
已弃用
首选方式是使用
MockitoJUnitRunner
或MockitoRule
为JUnit4
MockitoExtension
为了JUnit5
MockitoTestNGListener
为了TestNG
如果你不能使用专用的跑步者/扩展,你可以使用MockitoSession
1. 使用 MockitoAnnotations.openMocks():
Mockito 2 中的MockitoAnnotations.initMock()
方法已弃用并替换为MockitoAnnotations.openMocks()
Mockito 3 中的方法。该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...
}
}
如果您需要/需要,其他答案很棒,并且包含更多详细信息。
除此之外,我想添加一个 TL;DR:
@RunWith(MockitoJUnitRunner.class)
@Rule public MockitoRule rule = MockitoJUnit.rule();
@Before public void initMocks() {
MockitoAnnotations.initMocks(this);
}
X x = mock(X.class)
(1) 和 (2) 和 (3) 是互斥的。
(4) 可与其他组合使用。