Mockito, JUnit and Spring

2022-08-31 12:47:04

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

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

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

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

此测试

@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,但是在这里Spring Integration Tests,创建Mock对象,我们建议改变Spring的上下文。ReflectionTestUtils.setField()

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

@InjectMocks
private MyTestObject testObject

@Mock
private MyDependentObject mockedObject

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

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

编辑:我不太清楚。我将提供3个代码示例来澄清我的自我:假设,我们有bean HelloWorld与方法和bean HelloFacade与方法,将调用转发到HelloWorld的方法。printHello()sayHelloprintHello()

第一个例子是使用Spring的上下文,没有自定义运行器,使用ReflemEstUtils进行依赖注入(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所指出的那样,有一种方法可以在不显式调用的情况下运行它。我还将放弃在这个例子中使用Spring的上下文。MockitoAnnotations.initMocks(this);

@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类而不是接口。


答案 1

老实说,我不确定我是否真的理解你的问题,:P我会尽可能多地澄清,从你最初的问题中得到什么:

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

您最初建议的方式,示例2,3正是在做我上面描述的事情。

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

1)在春季应用程序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 Integration Tests,创建模拟对象中描述的方式进行操作。这种方法是在Spring的应用程序上下文中创建模拟,您可以从应用程序ctx获取模拟对象以进行存根/验证:

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

    @Autowired
    TestTarget sut;

    @Autowired
    Foo mockFoo;

    @Test
    public void someTest() {
         // ....
    }
}

这两种方式都应该有效。主要区别在于,前一种情况是在经历弹簧的生命周期等(例如bean初始化)后注入依赖关系,而后一种情况是事先注入的。例如,如果你的SUT实现了spring的初始化Bean,并且初始化例程涉及依赖关系,你会看到这两种方法之间的区别。我相信这两种方法没有对错之分,只要你知道自己在做什么。

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


答案 2

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

使用反射测试工具的示例 1 不是单元测试的好方法。你真的不想为单元测试加载弹簧上下文。只需模拟并注入所需的内容,如其他示例所示。

如果你想做一些集成测试,你确实想加载弹簧上下文,但是我更喜欢使用来执行上下文的加载,以及如果你需要显式访问它的bean。@RunWith(SpringJUnit4ClassRunner.class)@Autowired

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

示例 3 是另一种不使用 的有效方法。您尚未显式实例化受测类,但本可以使用示例 2 执行相同的操作。@RunWith(...)HelloFacadeImpl

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


推荐