Programing

모의 객체 초기화-MockIto

crosscheck 2020. 8. 10. 07:40
반응형

모의 객체 초기화-MockIto


MockIto를 사용하여 모의 객체를 초기화하는 방법에는 여러 가지가 있습니다. 이들 중 가장 좋은 방법은 무엇입니까?

1.

 public class SampleBaseTestCase {

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

2.

@RunWith(MockitoJUnitRunner.class)

[편집] 3.

mock(XXX.class);

이것보다 더 나은 방법이 있다면 나에게 제안하십시오 ...


mocks 초기화의 경우 runner 또는를 사용하는 것은 완전히 MockitoAnnotations.initMocks동등한 솔루션입니다. MockitoJUnitRunner 의 javadoc에서 :

JUnit 4.5 runner initializes mocks annotated with Mock, so that explicit usage of MockitoAnnotations.initMocks(Object) is not necessary. Mocks are initialized before each test method.


첫 번째 솔루션 ( MockitoAnnotations.initMocks사용 SpringJUnit4ClassRunner)은 테스트 케이스에서 특정 실행기 ( :)를 이미 구성한 경우 사용할 수 있습니다 .

두 번째 솔루션 (사용 MockitoJUnitRunner)은 더 고전적이고 내가 가장 좋아하는 솔루션 입니다. 코드는 더 간단합니다. 러너를 사용하면 프레임 워크 사용에 대한 자동 유효성 검사의 큰 이점이 있습니다 ( 이 답변 에서 @David Wallace의해 설명 됨 ).

두 솔루션 모두 테스트 방법간에 모의 (및 ​​스파이)를 공유 할 수 있습니다. 와 함께 사용하면 @InjectMocks단위 테스트를 매우 빠르게 작성할 수 있습니다. 상용구 모의 코드가 줄어들고 테스트가 더 읽기 쉽습니다. 예를 들면 :

@RunWith(MockitoJUnitRunner.class)
public class ArticleManagerTest {

    @Mock private ArticleCalculator calculator;
    @Mock(name = "database") private ArticleDatabase dbMock;
    @Spy private UserProvider userProvider = new ConsumerUserProvider();

    @InjectMocks private ArticleManager manager;

    @Test public void shouldDoSomething() {
        manager.initiateArticle();
        verify(database).addListener(any(ArticleListener.class));
    }

    @Test public void shouldDoSomethingElse() {
        manager.finishArticle();
        verify(database).removeListener(any(ArticleListener.class));
    }
}

장점 : 코드가 최소화 됨

단점 : 흑 마법. IMO는 주로 @InjectMocks 주석 때문입니다. 이 주석을 사용하면 "코드의 고통있습니다" ( @Brice 의 훌륭한 댓글 참조 )


세 번째 해결책은 각 테스트 방법에 대한 모의를 만드는 것입니다. @mlk의해 설명 된 대로 " 자체 테스트 " 를 가질 수 있습니다 .

public class ArticleManagerTest {

    @Test public void shouldDoSomething() {
        // given
        ArticleCalculator calculator = mock(ArticleCalculator.class);
        ArticleDatabase database = mock(ArticleDatabase.class);
        UserProvider userProvider = spy(new ConsumerUserProvider());
        ArticleManager manager = new ArticleManager(calculator, 
                                                    userProvider, 
                                                    database);

        // when 
        manager.initiateArticle();

        // then 
        verify(database).addListener(any(ArticleListener.class));
    }

    @Test public void shouldDoSomethingElse() {
        // given
        ArticleCalculator calculator = mock(ArticleCalculator.class);
        ArticleDatabase database = mock(ArticleDatabase.class);
        UserProvider userProvider = spy(new ConsumerUserProvider());
        ArticleManager manager = new ArticleManager(calculator, 
                                                    userProvider, 
                                                    database);

        // when 
        manager.finishArticle();

        // then
        verify(database).removeListener(any(ArticleListener.class));
    }
}

장점 : API 작동 방식을 명확하게 보여줍니다 (BDD ...)

단점 : 더 많은 상용구 코드가 있습니다. (모의 생성)


나의 추천 은 타협입니다. @Mock와 함께 주석을 사용 @RunWith(MockitoJUnitRunner.class)하되는 사용하지 마십시오 @InjectMocks.

@RunWith(MockitoJUnitRunner.class)
public class ArticleManagerTest {

    @Mock private ArticleCalculator calculator;
    @Mock private ArticleDatabase database;
    @Spy private UserProvider userProvider = new ConsumerUserProvider();

    @Test public void shouldDoSomething() {
        // given
        ArticleManager manager = new ArticleManager(calculator, 
                                                    userProvider, 
                                                    database);

        // when 
        manager.initiateArticle();

        // then 
        verify(database).addListener(any(ArticleListener.class));
    }

    @Test public void shouldDoSomethingElse() {
        // given
        ArticleManager manager = new ArticleManager(calculator, 
                                                    userProvider, 
                                                    database);

        // when 
        manager.finishArticle();

        // then 
        verify(database).removeListener(any(ArticleListener.class));
    }
}

장점 : API 작동 방식을 명확하게 보여줍니다 (내가 ArticleManager인스턴스화되는 방식). 상용구 코드가 없습니다.

단점 : 테스트는 자체 포함되지 않으며 코드의 고통이 적습니다.


이제 (v1.10.7부터) MockitoRule 이라는 JUnit4 규칙을 사용하는 모의를 인스턴스화하는 네 번째 방법이 있습니다.

@RunWith(JUnit4.class)   // or a different runner of your choice
public class YourTest
  @Rule public MockitoRule rule = MockitoJUnit.rule();
  @Mock public YourMock yourMock;

  @Test public void yourTestMethod() { /* ... */ }
}

JUnit looks for subclasses of TestRule annotated with @Rule, and uses them to wrap the test Statements that the Runner provides. The upshot of this is that you can extract @Before methods, @After methods, and even try...catch wrappers into rules. You can even interact with these from within your test, the way that ExpectedException does.

MockitoRule behaves almost exactly like MockitoJUnitRunner, except that you can use any other runner, such as Parameterized (which allows your test constructors to take arguments so your tests can be run multiple times), or Robolectric's test runner (so its classloader can provide Java replacements for Android native classes). This makes it strictly more flexible to use in recent JUnit and Mockito versions.

In summary:

  • Mockito.mock(): Direct invocation with no annotation support or usage validation.
  • MockitoAnnotations.initMocks(this): Annotation support, no usage validation.
  • MockitoJUnitRunner: Annotation support and usage validation, but you must use that runner.
  • MockitoRule: Annotation support and usage validation with any JUnit runner.

See also: How JUnit @Rule works?


There is a neat way of doing this.

  • If it's an Unit Test you can do this:

    @RunWith(MockitoJUnitRunner.class)
    public class MyUnitTest {
    
        @Mock
        private MyFirstMock myFirstMock;
    
        @Mock
        private MySecondMock mySecondMock;
    
        @Spy
        private MySpiedClass mySpiedClass = new MySpiedClass();
    
        // It's gonna inject the 2 mocks and the spied object per reflection to this object
        // The java doc of @InjectMocks explains it really well how and when it does the injection
        @InjectMocks
        private MyClassToTest myClassToTest;
    
        @Test
        public void testSomething() {
        }
    }
    
  • EDIT: If it's an Integration test you can do this(not intended to be used that way with Spring. Just showcase that you can initialize mocks with diferent Runners):

    @RunWith(SpringJUnit4ClassRunner.class)
    @ContextConfiguration("aplicationContext.xml")
    public class MyIntegrationTest {
    
        @Mock
        private MyFirstMock myFirstMock;
    
        @Mock
        private MySecondMock mySecondMock;
    
        @Spy
        private MySpiedClass mySpiedClass = new MySpiedClass();
    
        // It's gonna inject the 2 mocks and the spied object per reflection to this object
        // The java doc of @InjectMocks explains it really well how and when it does the injection
        @InjectMocks
        private MyClassToTest myClassToTest;
    
        @Before
        public void setUp() throws Exception {
              MockitoAnnotations.initMocks(this);
        }
    
        @Test
        public void testSomething() {
        }
    }
    

MockitoAnnotations & the runner have been well discussed above, so I'm going to throw in my tuppence for the unloved:

XXX mockedXxx = mock(XXX.class);

I use this because I find it a little bit more descriptive and I prefer (not out right ban) unit tests not to use member variables as I like my tests to be (as much as they can be) self contained.


The other answers are great and contain more detail if you want/need them.
In addition to those, I would like to add a TL;DR:

  1. Prefer to use
    • @RunWith(MockitoJUnitRunner.class)
  2. If you cannot (because you already use a different runner), prefer to use
    • @Rule public MockitoRule rule = MockitoJUnit.rule();
  3. Similar to (2), but you should not use this anymore:
    • @Before public void initMocks() { MockitoAnnotations.initMocks(this); }
  4. If you want to use a mock in just one of the tests and don't want to expose it to other tests in the same test class, use
    • X x = mock(X.class)

(1) and (2) and (3) are mutually exclusive.
(4) can be used in combination with the others.

참고URL : https://stackoverflow.com/questions/15494926/initialising-mock-objects-mockito

반응형