모의 객체 초기화-MockIto


122

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

1.

 public class SampleBaseTestCase {

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

2.

@RunWith(MockitoJUnitRunner.class)

[편집] 3.

mock(XXX.class);

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

답변:


153

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인스턴스화되는 방식). 상용구 코드가 없습니다.

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


하지만 주석은 유용하지만 열악한 OO 디자인을 만들거나 저하시키는 것으로부터 보호하지 못합니다. 개인적으로는 상용구 코드를 줄여서 기쁘지만 디자인을 더 나은 것으로 바꾸는 계기가되는 코드 (또는 PITA)의 고통을 덜어 주어 나와 팀이 OO 디자인에 주목하고 있습니다. 나는 SOLID 디자인이나 GOOS 아이디어와 같은 원칙으로 OO 디자인을 따르는 것이 모의 인스턴스화 방법을 선택하는 것보다 훨씬 더 중요하다고 생각합니다.
Brice 2013 년

1
(후속)이 객체가 어떻게 생성되었는지 알 수 없다면 그것에 대한 고통을 느끼지 못하며, 새로운 기능을 추가해야한다면 미래의 프로그래머가 잘 반응하지 않을 수 있습니다. 어쨌든 그것은 두 가지 모두에서 논쟁의 여지가 있습니다. 나는 단지 그것에 대해 조심하라고 말하는 것입니다.
Brice 2013 년

6
이 두 가지가 동등하다는 것은 정확하지 않습니다. 더 간단한 코드가를 사용하는 유일한 이점이라는 것은 사실이 아닙니다 MockitoJUnitRunner. 차이점에 대한 자세한 내용은 stackoverflow.com/questions/10806345/… 의 질문 과 이에 대한 제 답변을 참조하십시오.
Dawood ibn Kareem

2
@Gontard 네, 확실히 종속성이 표시되지만이 접근 방식을 사용하여 코드가 잘못되는 것을 보았습니다. 를 사용하는 Collaborator collab = mock(Collaborator.class)것에 대해 제 생각에는이 방법이 확실히 유효한 접근 방식입니다. 이것은 장황한 경향이 있지만 테스트의 이해력과 리팩토링 가능성을 얻을 수 있습니다. 두 가지 방법 모두 장단점이 있지만 어떤 접근 방식이 더 나은지 아직 결정하지 않았습니다. Amyway는 항상 쓰레기를 작성할 수 있으며 아마도 컨텍스트와 코더에 따라 다릅니다.
Brice 2013 년

1
@mlk 나는 당신과 완전히 동의합니다. 내 영어는 그다지 좋지 않으며 뉘앙스가 부족합니다. 내 요점은 UNIT 단어를 고집하는 것이 었습니다.
gontard

30

이제 (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은 @Rule로 주석이 달린 TestRule의 하위 클래스를 찾고 이를 사용 하여 Runner가 제공하는 테스트 문래핑합니다 . 이것의 결론은 @Before 메소드, @After 메소드를 추출 할 수 있으며 심지어 try ... catch 래퍼를 규칙에 넣을 수 있다는 것입니다. ExpectedException 이 수행 하는 방식으로 테스트 내에서 이들과 상호 작용할 수도 있습니다 .

MockitoRule은 Parameterized 와 같은 다른 실행기를 사용할 수 있다는 점을 제외하면 MockitoJUnitRunner와 거의 동일하게 작동합니다. (테스트 생성자가 인수를 받아 테스트를 여러 번 실행할 수 있도록 허용) 또는 Robolectric의 테스트 실행기 합니다. Android 네이티브 클래스의 경우). 따라서 최신 JUnit 및 Mockito 버전에서 사용하는 것이 훨씬 더 유연 해집니다.

요약해서 말하자면:

  • Mockito.mock(): 어노테이션 지원 또는 사용 유효성 검증없이 직접 호출.
  • MockitoAnnotations.initMocks(this): 주석 지원, 사용 확인 없음.
  • MockitoJUnitRunner: 주석 지원 및 사용 유효성 검사,하지만 해당 러너를 사용해야합니다.
  • MockitoRule: 모든 JUnit 실행기를 사용한 주석 지원 및 사용 유효성 검증.

참조 : JUnit @Rule의 작동 원리


3
Kotlin에서 규칙은 다음과 같습니다.@get:Rule val mockitoRule: MockitoRule = MockitoJUnit.rule()
Cristan

10

이를 수행하는 깔끔한 방법이 있습니다.

  • 단위 테스트 인 경우 다음을 수행 할 수 있습니다.

    @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() {
        }
    }
  • 편집 : 통합 테스트 인 경우 다음을 수행 할 수 있습니다 (Spring에서는 그런 방식으로 사용할 수 없습니다. 다른 러너로 모의를 초기화 할 수 있음을 보여주세요) :

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

1
MOCK이 통합 테스트에도 참여한다면 말이 될까요?
VinayVeluri 2013 년

2
실제로는 그렇지 않습니다. Mockito의 가능성을 보여주고 싶었습니다. 예를 들어 RESTFuse를 사용하는 경우 러너를 사용하여 MockitoAnnotations.initMocks (this);
emd

8

MockitoAnnotations 및 주자는 위에서 잘 논의되었으므로 사랑받지 못하는 사람들을 위해 제 조롱을 던질 것입니다.

XXX mockedXxx = mock(XXX.class);

나는 그것이 조금 더 설명 적이기 때문에 이것을 사용하고, 내 테스트가 (가능한 한) 자체 포함되는 것을 좋아하기 때문에 멤버 변수를 사용하지 않는 (올바른 금지가 아닌) 단위 테스트를 선호하기 때문에 사용합니다.


mock (XX.class)를 사용하는 것보다 테스트 케이스를 자체 포함하는 것 외에 다른 이점이 있습니까?
VinayVeluri 2013 년

내가 아는 한은 아닙니다.
Michael Lloyd Lee mlk

3
테스트를 읽기 위해 이해해야 할 마법이 적습니다. 당신은 변수를 선언하고, 그것을 가치 제공 - 등 어떤 주석, 반사
카루

2

JUnit 5 Jupiter의 간단한 예인 "RunWith"가 제거되었으므로 이제 "@ExtendWith"주석을 사용하여 확장 기능을 사용해야합니다.

@ExtendWith(MockitoExtension.class)
class FooTest {

  @InjectMocks
  ClassUnderTest test = new ClassUnderTest();

  @Spy
  SomeInject bla = new SomeInject();
}

0

다른 답변은 훌륭하며 원하거나 필요한 경우 더 자세한 정보를 포함합니다.
그 외에도 TL; DR을 추가하고 싶습니다.

  1. 사용 선호
    • @RunWith(MockitoJUnitRunner.class)
  2. 이미 다른 러너를 사용하고 있기 때문에 할 수없는 경우
    • @Rule public MockitoRule rule = MockitoJUnit.rule();
  3. (2)하지만, 당신이해야 할 유사 하지 더 이상이 사용
    • @Before public void initMocks() { MockitoAnnotations.initMocks(this); }
  4. 테스트 중 하나에서만 mock을 사용하고 동일한 테스트 클래스의 다른 테스트에 노출하지 않으려면 다음을 사용하십시오.
    • X x = mock(X.class)

(1) 및 (2) 및 (3)은 상호 배타적입니다.
(4) 다른 것과 함께 사용할 수 있습니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.