Mockito,JUnit和Spring

我今天才开始学习Mockito。 我写了一些简单的testing(使用JUnit,见下文),但我不知道如何在Spring的pipe理bean中使用模拟对象。 与Spring合作的最佳实践是什么? 我应该如何向我的bean注入嘲弄的依赖关系?

你可以跳过这个直到回到我的问题

首先,我学到了什么。 这是非常好的文章Mocks不是解释基础的存根 (Mock的检查行为validation不是状态validation )。 那么这里有一个很好的例子,在这里Mockito 嘲笑与mockito我们有解释,Mockito的模拟对象都是模拟存根(stub)

这里的Mockito和这里的Matchers你可以find更多的例子。

这个testing

@Test public void testReal(){ List<String> mockedList = mock(List.class); //stubbing //when(mockedList.get(0)).thenReturn("first"); mockedList.get(anyInt()); OngoingStubbing<String> stub= when(null); stub.thenReturn("first"); //String res = mockedList.get(0); //System.out.println(res); //you can also verify using argument matcher //verify(mockedList).get(anyInt()); verify(mockedList); mockedList.get(anyInt()); } 

工作得很好。

回到我的问题。 这里注入Mockito到一个Spring bean有人尝试使用Springs ReflectionTestUtils.setField() ,但在这里比Spring集成testing,创build模拟对象,我们build议改变 Spring的上下文。

我真的不明白最后两个链接…有人可以解释我有什么问题与Mockito的? 这个解决scheme有什么问题?

 @InjectMocks private MyTestObject testObject @Mock private MyDependentObject mockedObject @Before public void setup() { MockitoAnnotations.initMocks(this); } 

https://stackoverflow.com/a/8742745/1137529

编辑 :我不是很清楚。 我将提供3个代码示例来说明我的自我:假设我们有方法printHello() Bean HelloWorld和方法sayHello Bean HelloFacade,它们将调用转发给HelloWorld的方法printHello()

第一个例子是使用Spring的上下文,没有自定义运行器,使用ReflectionTestUtils进行dependency injection(DI):

 public class Hello1Test { private ApplicationContext ctx; @Before public void setUp() { MockitoAnnotations.initMocks(this); this.ctx = new ClassPathXmlApplicationContext("META-INF/spring/ServicesImplContext.xml"); } @Test public void testHelloFacade() { HelloFacade obj = (HelloFacade) ctx.getBean(HelloFacadeImpl.class); HelloWorld mock = mock(HelloWorld.class); doNothing().when(mock).printHello(); ReflectionTestUtils.setField(obj, "hello", mock); obj.sayHello(); verify(mock, times(1)).printHello(); } } 

正如@Noam指出的那样,运行它没有明确的调用MockitoAnnotations.initMocks(this); 。 在这个例子中,我也将放弃使用Spring的上下文。

 @RunWith(MockitoJUnitRunner.class) public class Hello1aTest { @InjectMocks private HelloFacade obj = new HelloFacadeImpl(); @Mock private HelloWorld mock; @Test public void testHelloFacade() { doNothing().when(mock).printHello(); obj.sayHello(); } } 

另一种方法来做到这一点

 public class Hello1aTest { @Before public void setUp() { MockitoAnnotations.initMocks(this); } @InjectMocks private HelloFacadeImpl obj; @Mock private HelloWorld mock; @Test public void testHelloFacade() { doNothing().when(mock).printHello(); obj.sayHello(); } } 

那么,在那个典型的例子中,我们必须手动创buildHelloFacadeImpl并将其分配给HelloFacade,因为HelloFacade是接口。 在最后一个例子中,我们可以声明HelloFacadeImpl,Mokito会为我们实例化它。 这种方法的缺点是,现在unit testing是impl级而不是接口。

老实说,我不确定我是否真的明白你的问题:我会尽可能地澄清你的问题,

首先,在大多数情况下,你不应该对Spring有任何顾虑。 你很less需要在编写你的unit testing时涉及到spring。 在正常情况下,你只需要在你的unit testing中实例化被测系统(SUT,待测目标),并在testing中注入SUT的依赖关系。 依赖关系通常是一个模拟/存根。

你原来的build议的方式,例子2,3正在做我上面描述的。

在一些罕见的情况下(比如集成testing或者一些特殊的unit testing),你需要创build一个Spring应用上下文,并从应用上下文获取你的SUT。 在这种情况下,我相信你可以:

1)在spring的应用程序ctx创build你的SUT,得到它的参考,并注入嘲笑

 @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration("test-app-ctx.xml") public class FooTest { @Autowired @InjectMocks TestTarget sut; @Mock Foo mockFoo; @Before /* Initialized mocks */ public void setup() { MockitoAnnotations.initMocks(this); } @Test public void someTest() { // .... } } 

要么

2)遵循链接Spring集成testing,创build模拟对象中描述的方式。 这种方法是在Spring的应用上下文中创buildmock,你可以从app ctx获取模拟对象来做你的存根/validation:

 @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration("test-app-ctx.xml") public class FooTest { @Autowired TestTarget sut; @Autowired Foo mockFoo; @Test public void someTest() { // .... } } 

这两种方式应该工作。 主要区别在于前一种情况会在经过Spring的生命周期之后注入依赖项(例如bean初始化),而后一种情况是在注入之前注入的。 例如,如果你的SUT实现了Spring的InitializingBean,并且初始化例程涉及依赖关系,你将会看到这两种方法之间的区别。 我相信,只要你知道你在做什么,这两种方法是没有对错的。

只是一个补充,@Mock,@Inject,MocktoJunitRunner等都是不必要的使用Mockito。 他们只是实用程序,以节省您键入Mockito.mock(Foo.class)和一堆setter调用。

你的问题似乎在问你提供的三个例子中哪一个是首选的方法。

示例1使用reflectionTestUtils不是unit testing的好方法。 你真的不想为unit testing加载spring context。 只是模拟和注入所需的其他例子所示。

如果你想做一些集成testing ,你可以加载Spring上下文,但是如果你需要显式地访问它的bean,我更喜欢使用@RunWith(SpringJUnit4ClassRunner.class)@Autowired一起加载上下文。

示例2是一种有效的方法,使用@RunWith(MockitoJUnitRunner.class)将删除指定@Before方法和显式调用MockitoAnnotations.initMocks(this);

例3是另一个不使用@RunWith(...)有效方法。 你没有明确地实例化你的类下的HelloFacadeImpl类,但你可以像例2一样。

我的build议是使用示例2进行unit testing,因为它减less了代码混乱。 如果你不得不这样做,你可以回到更详细的configuration。

你真的不需要MockitoAnnotations.initMocks(this); 如果你使用的是mockito 1.9(或更新版本) – 你只需要这个:

 @InjectMocks private MyTestObject testObject; @Mock private MyDependentObject mockedObject; 

@InjectMocks注解将注入所有的MyTestObjectMyTestObject对象。

在Spring 4.2.RC1中引入一些新的testing工具可以让我们编写不依赖于SpringJUnit4ClassRunner Spring集成testing。 看看这部分的文档。

在你的情况下,你可以写你的Spring集成testing,并仍然使用像这样的模拟:

 @RunWith(MockitoJUnitRunner.class) @ContextConfiguration("test-app-ctx.xml") public class FooTest { @ClassRule public static final SpringClassRule SPRING_CLASS_RULE = new SpringClassRule(); @Rule public final SpringMethodRule springMethodRule = new SpringMethodRule(); @Autowired @InjectMocks TestTarget sut; @Mock Foo mockFoo; @Test public void someTest() { // .... } } 

这是我的简短摘要。

如果你想编写一个unit testing,不要使用Spring的applicationContext,因为你不想在你要进行unit testing的类中注入任何真正的依赖关系。 而是使用mock,或者在类的顶部使用@RunWith(MockitoJUnitRunner.class)注释,或者在@Before方法使用MockitoAnnotations.initMocks(this)

如果你想写一个集成testing,使用:

 @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration("yourTestApplicationContext.xml") 

例如,使用内存数据库设置应用程序上下文。 通常你不会在集成testing中使用mock,但是你可以通过使用上面介绍的MockitoAnnotations.initMocks(this)方法来实现。

您是否必须实例化@InjectMocks注释字段的区别在于@InjectMocks的版本,而不是您是否使用MockitoJunitRunner或MockitoAnnotations.initMocks 。 在1.9中,它也会处理你的@Mock域的一些构造器注入,它会为你做实例化。 在早期版本中,你必须自己实例化它。

这就是我对Spring bean进行unit testing的方法。 没有问题。 当人们想要使用Springconfiguration文件实际执行注入模拟时,人们会感到困惑,而这正是unit testing和集成testing的关键。

当然 ,被testing的单位是一个Impl。 你需要testing一个真实的具体的东西,对吧? 即使你把它声明为一个接口,你也必须实例化真实的东西来testing它。 现在,你可以进入间谍,这是围绕真实对象的存根/模拟包装,但这应该是对于angular落的情况。

如果你将你的项目迁移到Spring Boot 1.4,你可以使用新的批注@MockBean来伪装MyDependentObject 。 使用该function,您可以从您的testing中删除Mockito的@Mock@InjectMocks注释。