Answers:
对于模拟初始化,使用Runner或是MockitoAnnotations.initMocks
严格等效的解决方案。从MockitoJUnitRunner的javadoc中:
JUnit 4.5 runner initializes mocks annotated with Mock, so that explicit usage of MockitoAnnotations.initMocks(Object) is not necessary. Mocks are initialized before each test method.
MockitoAnnotations.initMocks
当您已经SpringJUnit4ClassRunner
在测试用例上配置了特定的运行器时,可以使用第一个解决方案(带有)。
第二个解决方案(带有MockitoJUnitRunner
)更经典,也是我的最爱。代码更简单。使用跑步者可提供以下优势框架使用的自动验证(通过描述 @大卫华莱士在这个答案)。
两种解决方案都可以在测试方法之间共享模拟(和间谍)。与结合使用@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
实例化)。没有样板代码。
缺点:测试不是独立的,代码痛苦更少
MockitoJUnitRunner
。有关差异的更多信息,请参见stackoverflow.com/questions/10806345/…上的问题以及我的答案。
Collaborator collab = mock(Collaborator.class)
我认为以这种方式使用,肯定是有效的方法。尽管这可能很冗长,但您可以提高测试的可理解性和重构性。两种方法各有利弊,我尚未确定哪种方法更好。Amyway总是可以编写废话,并且可能取决于上下文和编码器。
(从v1.10.7开始)现在有第四种实例化模拟的方法,该方法使用JUnit4 规则。称为MockitoRule。
@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方法,甚至尝试...将包装器捕获到规则中。您甚至可以在测试中与这些对象进行交互,即ExpectedException一样。
MockitoRule的行为 几乎与MockitoJUnitRunner一样,不同之处在于您可以使用其他任何运行程序,例如Parameterized(允许测试构造函数接受参数,以便您的测试可以多次运行)或Robolectric的测试运行程序(因此其类加载器可以提供Java替换)适用于Android本机类)。这使得在最新的JUnit和Mockito版本中使用时更加灵活。
综上所述:
Mockito.mock()
:直接调用,没有注释支持或用法验证。MockitoAnnotations.initMocks(this)
:支持注释,无使用验证。MockitoJUnitRunner
:注释支持和使用验证,但是您必须使用该运行器。MockitoRule
:注释支持和任何JUnit运行器的使用验证。另请参阅:JUnit @Rule如何工作?
@get:Rule val mockitoRule: MockitoRule = MockitoJUnit.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一起使用。仅演示可以使用不同的Runners初始化模拟):
@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和跑步者进行了很好的讨论,所以我要为那些未被爱的人大声疾呼:
XXX mockedXxx = mock(XXX.class);
我之所以使用它,是因为我发现它更具描述性,并且我更喜欢(并非绝对禁止)单元测试,不要使用成员变量,因为我希望自己的测试(尽可能多地包含)。
其他答案很好,如果您需要/需要它们,请提供更多详细信息。
除了这些,我还要添加一个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)可以与其他结合使用。