初始化模拟对象-MockIto


122

有很多方法可以使用MockIto初始化模拟对象。其中最好的方法是什么?

1。

 public class SampleBaseTestCase {

   @Before public void initMocks() {
       MockitoAnnotations.initMocks(this);
   }

2。

@RunWith(MockitoJUnitRunner.class)

[编辑] 3。

mock(XXX.class);

如果有其他方法可以建议我...

Answers:


153

对于模拟初始化,使用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实例化)。没有样板代码。

缺点:测试不是独立的,代码痛苦更少


但是请注意,注释很有用,但它们不能保护您避免设计不良的OO设计(或使其退化)。就我个人而言,虽然我很高兴减少样板代码,但我放松了代码(或PITA)的痛苦,而这正是将设计更改为更好的代码的触发因素,因此我和团队都在关注OO设计。我觉得遵循OO设计并遵循诸如SOLID设计或GOOS创意之类的原则比选择如何实例化模拟更为重要。
布莱斯

1
(后续操作)如果看不到如何创建该对象,就不会感到痛苦,并且如果应该添加新功能,将来的程序员可能反应不佳。无论如何,两种方式都存在争议,我只是说要小心。
2013年

6
这两个等同是不正确的。较简单的代码是使用的唯一优势是不正确的MockitoJUnitRunner。有关差异的更多信息,请参见stackoverflow.com/questions/10806345/…上的问题以及我的答案。
2013年

2
@Gontard是的,可以确定依赖项是可见的,但是我已经看到使用这种方法的代码出错了。Collaborator collab = mock(Collaborator.class)我认为以这种方式使用,肯定是有效的方法。尽管这可能很冗长,但您可以提高测试的可理解性和重构性。两种方法各有利弊,我尚未确定哪种方法更好。Amyway总是可以编写废话,并且可能取决于上下文和编码器。
2013年

1
@mlk我完全同意你的看法。我的英语不是很好,缺乏细微差别。我的意思是坚持使用UNIT这个词。
贡塔德

30

(从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如何工作?


3
在科特林,规则如下所示:@get:Rule val mockitoRule: MockitoRule = MockitoJUnit.rule()
克里斯坦(Cristan)

10

有一种整齐的方法可以做到这一点。

  • 如果是单元测试,则可以执行以下操作:

    @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() {
        }
    }

1
如果MOCK也参与了集成测试,是否有意义?
VinayVeluri

2
实际上不会,您的权利。我只是想展示Mockito的可能性。例如,如果您使用RESTFuse,则必须使用其跑步者,以便可以使用MockitoAnnotations.initMocks(this);初始化模拟。
2013年

8

上面已经对MockitoAnnotations和跑步者进行了很好的讨论,所以我要为那些未被爱的人大声疾呼:

XXX mockedXxx = mock(XXX.class);

我之所以使用它,是因为我发现它更具描述性,并且我更喜欢(并非绝对禁止)单元测试,不要使用成员变量,因为我希望自己的测试(尽可能多地包含)。


除了使测试用例自成一体之外,使用嘲笑(XX.class)是否还有其他优势?
VinayVeluri

据我所知。
Michael Lloyd Lee mlk 2013年

3
为了阅读测试,必须了解的魔术更少。您声明变量,并给它一个值-没有注释,反射等
卡鲁

2

对于JUnit 5 Jupiter的一个小示例,“ RunWith”已被删除,您现在需要使用“ @ExtendWith”注释来使用扩展。

@ExtendWith(MockitoExtension.class)
class FooTest {

  @InjectMocks
  ClassUnderTest test = new ClassUnderTest();

  @Spy
  SomeInject bla = new SomeInject();
}

0

其他答案很好,如果您需要/需要它们,请提供更多详细信息。
除了这些,我还要添加一个TL; DR:

  1. 喜欢使用
    • @RunWith(MockitoJUnitRunner.class)
  2. 如果不能(因为您已经使用了其他跑步者),则最好使用
    • @Rule public MockitoRule rule = MockitoJUnit.rule();
  3. 与(2)类似,但您不应使用它:
    • @Before public void initMocks() { MockitoAnnotations.initMocks(this); }
  4. 如果您只想在其中一个测试中使用模拟,并且不想将其暴露给同一测试类中的其他测试,请使用
    • X x = mock(X.class)

(1)和(2)和(3)是互斥的。
(4)可以与其他结合使用。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.