Mockito,JUnit和Spring

ale*_*ail 75 java spring unit-testing mockito

我今天才开始了解Mockito.我写了一些简单的测试(使用JUnit,见下文),但我无法弄清楚如何在Spring的托管bean中使用模拟对象.使用Spring的最佳实践是什么?我应该如何向我的bean注入模拟依赖?

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

首先,我学到了什么.这是非常好的文章Mocks Are Not Stubs解释了基础知识(Mock的检查行为验证不是状态验证).然后有一个很好的例子Mockito 和这里更容易嘲笑mockito.我们有解释说Mockito的模拟对象都是模拟存根.

这里的Mockito这里匹配器,你可以找到更多的例子.

这个测试

@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());
}
Run Code Online (Sandbox Code Playgroud)

工作得很好.

回到我的问题.在这里注入Mockito模拟到Spring bean有人试图使用Springs ReflectionTestUtils.setField(),但是在这里Spring Integration Tests,创建模拟对象我们建议改变 Spring的上下文.

我真的不明白最后两个链接......有人可以向我解释一下Spring与Mockito有什么问题吗?这个解决方案有什么问题?

@InjectMocks
private MyTestObject testObject

@Mock
private MyDependentObject mockedObject

@Before
public void setup() {
        MockitoAnnotations.initMocks(this);
}
Run Code Online (Sandbox Code Playgroud)

/sf/answers/611992181/

编辑:我不是很清楚.我将提供3个代码示例以阐明我的自我:假设,我们有bean HelloWorld with method printHello()和bean HelloFacade with method sayHelloto forward调用HelloWorld的方法printHello().

第一个例子是使用Spring的上下文而没有自定义运行器,使用ReflectionTestUtils进行依赖注入(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();
}

}
Run Code Online (Sandbox Code Playgroud)

正如@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();
}

}
Run Code Online (Sandbox Code Playgroud)

另一种方法

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();
}

}
Run Code Online (Sandbox Code Playgroud)

不,在那个例子中,我们必须手动实现HelloFacadeImpl并将其分配给HelloFacade,因为HelloFacade是接口.在最后一个例子中,我们可以声明HelloFacadeImpl,Mokito将为我们实例化它.这种方法的缺点是,现在,被测单元是impl-class而不是接口.

Adr*_*hum 53

老实说,我不确定我是否真的理解你的问题:PI将尽可能多地澄清我从原始问题中得到的结论:

首先,在大多数情况下,你不应该对Spring有任何担忧.你很少需要参与编写单元测试.在正常情况下,您只需要在单元测试中实例化被测系统(SUT,要测试的目标),并在测试中注入SUT的依赖关系.依赖项通常是mock/stub.

你原来建议的方式,例2,3,正是我上面所描述的.

在一些罕见的情况下(例如,集成测试或一些特殊的单元测试),您需要创建一个Spring应用程序上下文,并从应用程序上下文中获取您的SUT.在这种情况下,我相信你可以:

1)在spring app ctx中创建你的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() {
         // ....
    }
}
Run Code Online (Sandbox Code Playgroud)

要么

2)按照链接Spring Integration Tests,Creating Mock Objects中描述的方式进行操作.这种方法是在Spring的app环境中创建模拟,你可以从app ctx获取模拟对象来进行存根/验证:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("test-app-ctx.xml")
public class FooTest {

    @Autowired
    TestTarget sut;

    @Autowired
    Foo mockFoo;

    @Test
    public void someTest() {
         // ....
    }
}
Run Code Online (Sandbox Code Playgroud)

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

只是补充,@ Mock,@ Inject,MocktoJunitRunner等在使用Mockito时都是不必要的.它们只是保存您键入Mockito.mock(Foo.class)和一组setter调用的实用工具.

  • 我确实在`@ Before`中加入了'MockitoAnnotations.initMocks(this)`.另外,删除`@Autowired`(因此只留下`@InjectMocks`)确实给了我`TestTarget`中的模拟.(但是删除`@Autowired`也会让所有其他bean都没有被Spring初始化.)然而,更多的调查显示我需要一个`TestTarget #setFoo(Foo f)`方法.没有它,`@InjectMocks`工作正常,*除非*与`@Autowired`结合使用.所以:**同时使用`@Autowired`和`@InjectMocks`,然后`@Autowired private Foo mockFoo;`还不够.**可能需要一个错误报告; 将调查. (6认同)
  • 我不认为1)有效.那就是:当同时使用`@Autowired`和`@InjectMocks`时,我在`TestTarget`中看到Spring注入的bean,而不是模拟器.(我希望使用这两个注释只会*为`Foo`注入一个模拟,但仍然使用默认的Spring注入的bean用于在`TestTarget`中自动连接的所有其他依赖项,但不会在集成中模拟测试.看来没有雪茄.春季3.1.2; Mockito 1.9.5) (2认同)

Bra*_*rad 6

您的问题似乎在于询问您提供的三个示例中的哪一个是首选方法.

使用Reflection TestUtils的示例1不是单元测试的好方法.您真的不想为单元测试加载弹簧上下文.只需模拟并注入其他示例所示的内容即可.

如果你想进行一些集成测试,你确实想要加载spring上下文,但是如果你需要显式访问它的bean ,我更愿意使用@RunWith(SpringJUnit4ClassRunner.class)上下文的加载@Autowired.

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

示例3是另一种不使用的有效方法@RunWith(...).您没有HelloFacadeImpl明确地在测试中实例化您的类,但您可以使用示例2完成相同的操作.

我的建议是使用示例2进行单元测试,因为它减少了代码混乱.如果您被迫这样做,您可以回退到更详细的配置.