Java 莫基托、朱尼特和春天

Java 莫基托、朱尼特和春天,java,spring,unit-testing,mockito,Java,Spring,Unit Testing,Mockito,直到今天我才开始了解莫基托。我编写了一些简单的测试(使用JUnit,见下文),但我不知道如何在Spring的托管bean中使用模拟对象。使用Spring的最佳实践是什么。我应该如何向我的bean注入模拟依赖项 你可以跳过这个直到回到我的问题 首先,我学到了什么。 这是一篇很好的文章,它解释了基础知识(Mock的检查行为验证而不是状态验证)。这里有一个很好的例子 在这里。我们有一个解释,Mockito的mock对象是mock和stub 在这里和这里,你可以找到更多的例子 这个测试 @Test pu

直到今天我才开始了解莫基托。我编写了一些简单的测试(使用JUnit,见下文),但我不知道如何在Spring的托管bean中使用模拟对象。使用Spring的最佳实践是什么。我应该如何向我的bean注入模拟依赖项

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

首先,我学到了什么。 这是一篇很好的文章,它解释了基础知识(Mock的检查行为验证而不是状态验证)。这里有一个很好的例子 在这里。我们有一个解释,Mockito的mock对象是mock和stub

在这里和这里,你可以找到更多的例子

这个测试

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

编辑:我不是很清楚。我将提供3个代码示例来阐明我的观点: 假设我们有方法为
printHello()
的bean HelloWorld和方法为
sayHello
的bean HelloFade,它们将调用转发到HelloWorld的方法
printHello()

第一个示例是使用Spring的上下文,而不使用自定义运行程序,使用ReflectionTestUtils进行依赖项注入(DI):

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

}

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

您实际上不需要
MockitoAnnotations.initMocks(this)如果您使用的是mockito 1.9(或更新版本)-您只需要以下内容:

@InjectMocks
private MyTestObject testObject;

@Mock
private MyDependentObject mockedObject;

@InjectMocks
注释将把所有的mock注入
MyTestObject
对象。

是否必须实例化
@InjectMocks
注释字段的区别在于Mockito的版本,而不是使用MockitoJunitRunner还是
MockitoAnnotations.initMocks
。在1.9中,它还将处理
@Mock
字段的一些构造函数注入,它将为您进行实例化。在早期版本中,您必须自己实例化它

这就是我如何对SpringBean进行单元测试的方法。没问题。当人们想要使用Spring配置文件来实际执行模拟的注入时,他们会感到困惑,这是单元测试和集成测试的交叉点


当然,被测试的单元是一个Impl。你需要测试一个真正具体的东西,对吗?即使您将其声明为接口,您也必须实例化真实的对象来测试它。现在,您可以进入spies,它是围绕真实对象的存根/模拟包装器,但这应该适用于角落案例。

您的问题似乎是,您给出的三个示例中,哪一个是首选方法

示例1使用反射TestUtils不是单元测试的好方法。您根本不想为单元测试加载spring上下文。只需模拟并注入所需的内容,如其他示例所示

如果您想进行一些集成测试,您确实希望加载spring上下文,但是如果您需要显式访问其“bean”,我更希望使用
@RunWith(SpringJUnit4ClassRunner.class)
来执行上下文加载以及
@Autowired

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

示例3是另一种不使用
@RunWith(…)
的有效方法。您没有显式地实例化测试中的类
HelloFacadeImpl
,但是您可以对示例2执行同样的操作


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

老实说,我不确定我是否真的理解您的问题:p我将尽可能从您最初的问题中得到澄清:

首先,在大多数情况下,您不应该担心Spring。您很少需要让spring参与编写单元测试。在正常情况下,您只需要在单元测试中实例化被测试的系统(SUT,要测试的目标),并在测试中注入SUT的依赖项。依赖项通常是模拟/存根

您最初建议的方法,以及示例2、3,正是在做我上面描述的事情

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

1) 在spring应用程序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() {
         // ....
    }
}

2) 按照链接中描述的方式操作。这种方法是在Spring的应用程序上下文中创建模拟,您可以从应用程序ctx获取模拟对象来执行存根/验证:

@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等在我们身上都是不必要的
@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() {
         // ....
    }
}
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("test-app-ctx.xml")
public class FooTest {

    @Autowired
    TestTarget sut;

    @Autowired
    Foo mockFoo;

    @Test
    public void someTest() {
         // ....
    }
}
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("yourTestApplicationContext.xml")
@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() {
         // ....
    }
}