Programing

Mockito, JUnit 및 Spring

lottogame 2020. 10. 20. 07:14
반응형

Mockito, JUnit 및 Spring


나는 오늘만 Mockito에 대해 배우기 시작했습니다. 몇 가지 간단한 테스트 (JUnit 사용, 아래 참조)를 작성했지만 Spring의 관리 빈에서 모의 ​​객체를 어떻게 사용할 수 있는지 알 수 없습니다. Spring 작업을위한 모범 사례 는 무엇입니까? 내 빈에 모의 종속성을 어떻게 주입해야합니까?

내 질문으로 돌아갈 때까지 이것을 건너 뛸 수 있습니다 .

우선 내가 배운 것. 이것은 기본을 설명하는 매우 좋은 기사 Mocks Are n't Stubs 입니다 (Mock의 검사 동작 확인은 상태 확인이 아닙니다 ). 그런 다음 Mockito 와 여기 에 Mockito로 쉽게 조롱 하는 좋은 예가 있습니다 . Mockito의 mock 객체는 mockstub 모두라는 설명이 있습니다.

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

잘 작동합니다.

내 질문으로 돌아갑니다. 여기서 Injecting Mockito는 누군가 Springs를 사용하려고 시도 하는 Spring 빈에 모의ReflectionTestUtils.setField() 하지만 여기에서는 Spring 통합 테스트, Mock 객체 생성에 대해 Spring의 컨텍스트 변경 하도록 권장합니다 .

마지막 두 링크를 이해하지 못했습니다. 누군가 Mockito와 관련하여 Spring에 어떤 문제가 있는지 설명해 주시겠습니까? 이 솔루션의 문제점은 무엇입니까?

@InjectMocks
private MyTestObject testObject

@Mock
private MyDependentObject mockedObject

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

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

편집 : 나는 정말로 명확하지 않았습니다. 내 자신을 명확히하기 위해 3 가지 코드 예제를 제공 할 것입니다. 메서드가있는 HelloWorld printHello()빈과 sayHelloHelloWorld의 메서드 호출을 전달 하는 메서드 가있는 HelloFacade 빈이 있다고 가정 해 보겠습니다 printHello().

첫 번째 예는 Spring의 컨텍스트를 사용하고 사용자 지정 러너없이 DI (Dependency Injection)에 ReflectionTestUtils를 사용하는 것입니다.

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

}

아니요, 이전 예제에서는 HelloFacadeImpl을 수동으로 인스턴스화하고 HelloFacade에 할당해야합니다. HelloFacade가 인터페이스이기 때문입니다. 마지막 예제에서는 HelloFacadeImpl을 선언하기 만하면 Mokito가이를 인스턴스화합니다. 이 접근 방식의 단점은 현재 테스트 대상 단위가 인터페이스가 아니라 단순 클래스라는 것입니다.


솔직히 내가 귀하의 질문을 정말로 이해하는지 확실하지 않습니다 .PI는 귀하의 원래 질문에서 얻은 내용을 최대한 명확하게 설명하려고 노력할 것입니다.

첫째, 대부분의 경우 Spring에 대해 걱정할 필요가 없습니다. 단위 테스트를 작성하는 데 스프링이 필요하지 않습니다. 일반적인 경우에는 단위 테스트에서 테스트중인 시스템 (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) follow the way described in your link Spring Integration Tests, Creating Mock Objects. This approach is to create mocks in Spring's app context, and you can get the mock object from the app ctx to do your stubbing/verification:

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

    @Autowired
    TestTarget sut;

    @Autowired
    Foo mockFoo;

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

Both ways should work. The main difference is the former case will have the dependencies injected after going through spring's lifecycle etc. (e.g. bean initialization), while the latter case is injected beforehands. For example, if your SUT implements spring's InitializingBean, and the initialization routine involves the dependencies, you will see the difference between these two approach. I believe there is no right or wrong for these 2 approaches, as long as you know what you are doing.

Just a supplement, @Mock, @Inject, MocktoJunitRunner etc are all unnecessary in using Mockito. They are just utilities to save you typing the Mockito.mock(Foo.class) and bunch of setter invocations.


Your question seems to be asking about which of the three examples you have given is the preferred approach.

Example 1 using the Reflection TestUtils is not a good approach for Unit testing. You really don't want to be loading the spring context at all for a unit test. Just mock and inject what is required as shown by your other examples.

You do want to load the spring context if you want to do some Integration testing, however I would prefer using @RunWith(SpringJUnit4ClassRunner.class) to perform the loading of the context along with @Autowired if you need access to its' beans explicitly.

Example 2 is a valid approach and the use of @RunWith(MockitoJUnitRunner.class) will remove the need to specify a @Before method and an explicit call to MockitoAnnotations.initMocks(this);

Example 3 is another valid approach that doesn't use @RunWith(...). You haven't instantiated your class under test HelloFacadeImpl explicitly, but you could have done the same with Example 2.

My suggestion is to use Example 2 for your unit testing as it reduces the code clutter. You can fall back to the more verbose configuration if and when you're forced to do so.


The introduction of some new testing facilities in Spring 4.2.RC1 lets one write Spring integration tests that don't rely on the SpringJUnit4ClassRunner. Check out this part of the documentation.

In your case you could write your Spring integration test and still use mocks like this:

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

You don't really need the MockitoAnnotations.initMocks(this); if you're using mockito 1.9 ( or newer ) - all you need is this:

@InjectMocks
private MyTestObject testObject;

@Mock
private MyDependentObject mockedObject;

The @InjectMocks annotation will inject all your mocks to the MyTestObject object.


Here's my short summary.

If you want to write a unit test, don't use a Spring applicationContext because you don't want any real dependencies injected in the class you are unit testing. Instead use mocks, either with the @RunWith(MockitoJUnitRunner.class) annotation on top of the class, or with MockitoAnnotations.initMocks(this) in the @Before method.

If you want to write an integration test, use:

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

To set up your application context with an in-memory database for example. Normally you don't use mocks in integration tests, but you could do it by using the MockitoAnnotations.initMocks(this) approach described above.


The difference in whether you have to instantiate your @InjectMocks annotated field is in the version of Mockito, not in whether you use the MockitoJunitRunner or MockitoAnnotations.initMocks. In 1.9, which will also handle some constructor injection of your @Mock fields, it will do the instantiation for you. In earlier versions, you have to instantiate it yourself.

This is how I do unit testing of my Spring beans. There is no problem. People run into confusion when they want to use Spring configuration files to actually do the injection of the mocks, which is crossing up the point of unit tests and integration tests.

And of course the unit under test is an Impl. You need to test a real concrete thing, right? Even if you declared it as an interface you would have to instantiate the real thing to test it. Now, you could get into spies, which are stub/mock wrappers around real objects, but that should be for corner cases.


If you would migrate your project to Spring Boot 1.4, you could use new annotation @MockBean for faking MyDependentObject. With that feature you could remove Mockito's @Mock and @InjectMocks annotations from your test.

참고URL : https://stackoverflow.com/questions/10906945/mockito-junit-and-spring

반응형