Mockito를 사용하여 클래스의 모의 멤버 변수


136

나는 특히 개발과 단위 테스트에 초보자입니다. 내 요구 사항이 매우 간단하다고 생각하지만 다른 사람들의 생각을 알고 싶어합니다.

내가 두 개의 클래스를 가지고 있다고 가정 해보십시오.

public class First {

    Second second ;

    public First(){
        second = new Second();
    }

    public String doSecond(){
        return second.doSecond();
    }
}

class Second {

    public String doSecond(){
        return "Do Something";
    }
}

First.doSecond()방법 을 테스트 하기 위해 단위 테스트를 작성한다고 가정 해 봅시다 . 그러나 Second.doSecond()클래스 를 Mock하고 싶습니다. Mockito를 사용 하여이 작업을 수행하고 있습니다.

public void testFirst(){
    Second sec = mock(Second.class);
    when(sec.doSecond()).thenReturn("Stubbed Second");

    First first = new First();
    assertEquals("Stubbed Second", first.doSecond());
}

조롱이 적용되지 않고 어설 션이 실패한다는 것을 알았습니다. 테스트하려는 클래스의 멤버 변수를 조롱 할 수있는 방법이 없습니까? ?

답변:


86

모의를 전달할 수 있도록 멤버 변수에 액세스하는 방법을 제공해야합니다 (가장 일반적인 방법은 setter 메소드 또는 매개 변수를 취하는 생성자입니다).

코드에서이를 수행하는 방법을 제공하지 않으면 TDD (Test Driven Development)에 대해 잘못 고려됩니다.


4
감사. 나는 그것을 참조. 내부 메서드가 많을 수도 있지만 조롱해야 할 수도 있지만 미리 setXXX ()를 통해 설정해야 할 필요는없는 클래스가있는 경우 mock을 사용하여 통합 테스트를 수행하는 방법이 궁금합니다.
Anand Hemmige

2
테스트 구성으로 종속성 주입 프레임 워크를 사용하십시오. 만들고자하는 통합 테스트의 시퀀스 다이어그램을 작성하십시오. 시퀀스 다이어그램을 실제로 제어 할 수있는 객체로 고려하십시오. 즉, 위에 표시된 종속 객체 안티 패턴이있는 프레임 워크 클래스로 작업하는 경우 시퀀스 다이어그램 측면에서 객체와 잘못 팩토링 된 멤버를 단일 단위로 간주해야합니다. 제어 가능한 코드의 팩토링을 조정하여 테스트 할 수 있도록 준비하십시오.
키티리스트

9
친애하는 @kittylyst, 아마도 TDD 관점이나 모든 종류의 합리적인 관점에서 잘못되었을 것입니다. 그러나 때로는 개발자가 전혀 이해가되지 않는 장소에서 일하는 경우가 있습니다. 개발자가 지정한 유일한 목표는 할당 한 스토리를 완성하고 떠나는 것입니다. 그렇습니다, 그것은 틀 렸습니다, 말이되지 않습니다, 자격이없는 사람들은 중요한 결정과 그 모든 것을 취합니다. 따라서 하루가 끝나면 반 패턴이 많이 이깁니다.
amanas

1
클래스 멤버가 외부에서 설정할 이유가 없다면 왜 테스트 목적으로 세터를 만들어야합니까? 여기서 'Second'클래스가 실제로 파일 시스템 관리자 또는 도구이며, 테스트 할 객체를 구성하는 동안 초기화되었다고 상상해보십시오. First 클래스를 테스트하기 위해이 FileSystem Manager를 조롱 해야하는 모든 이유가 있으며 액세스 할 수있는 이유는 없습니다. 파이썬 에서이 작업을 수행 할 수 있으므로 Mockito로 어떻습니까?
Zangdar

65

코드를 변경할 수 없으면 불가능합니다. 그러나 의존성 주입을 좋아하고 Mockito가 지원합니다.

public class First {    
    @Resource
    Second second;

    public First() {
        second = new Second();
    }

    public String doSecond() {
        return second.doSecond();
    }
}

당신의 시험 :

@RunWith(MockitoJUnitRunner.class)
public class YourTest {
   @Mock
   Second second;

   @InjectMocks
   First first = new First();

   public void testFirst(){
      when(second.doSecond()).thenReturn("Stubbed Second");
      assertEquals("Stubbed Second", first.doSecond());
   }
}

이것은 매우 좋고 쉽습니다.


2
InjectMocks 때문에 이것이 다른 것보다 더 나은 대답이라고 생각합니다.
sudocoder

나 자신과 같은 테스트 초보자로서 특정 라이브러리와 프레임 워크를 신뢰하는 방법이 재미 있습니다. 나는 당신이 나에게 보여 주었다까지 ... 이것은 단지 재 설계 할 필요를 나타내는 나쁜 아이디어라고 가정하고 있다 (매우 명확하고 깨끗하게) Mockito에서 실제로 가능.
마이크 설치류

9
@Resource 는 무엇입니까 ?
IgorGanapolsky

3
@IgorGanapolsky @ Resource는 Java Spring 프레임 워크에서 작성 / 사용되는 주석입니다. Spring에 표시하는 방법은 Spring이 관리하는 Bean / Object입니다. stackoverflow.com/questions/4093504/resource-vs-autowired baeldung.com/spring-annotations-resource-inject-autowire 그것은 모의 일이 아니지만 비 테스트 클래스에서 사용되기 때문에 테스트.
Grez.Kev

이 답변을 이해하지 못합니다. 가능하지 않다고 말하면 가능하다는 것을 보여 주나요? 여기서 불가능한 것은 무엇입니까?
Goldname

35

코드를 자세히 보면 second테스트 의 속성이 여전히 Secondmock이 아닌 의 인스턴스 라는 것을 알 수 first있습니다 (코드에서 mock을 전달하지 않음 ).

가장 간단한 방법은 클래스 second에서 setter를 만들고 First모의 객체를 명시 적으로 전달하는 것입니다.

이처럼 :

public class First {

Second second ;

public First(){
    second = new Second();
}

public String doSecond(){
    return second.doSecond();
}

    public void setSecond(Second second) {
    this.second = second;
    }


}

class Second {

public String doSecond(){
    return "Do Something";
}
}

....

public void testFirst(){
Second sec = mock(Second.class);
when(sec.doSecond()).thenReturn("Stubbed Second");


First first = new First();
first.setSecond(sec)
assertEquals("Stubbed Second", first.doSecond());
}

다른 하나는 Second인스턴스를 First생성자 매개 변수 로 전달하는 것 입니다.

코드를 수정할 수 없다면 리플렉션을 사용하는 것이 유일한 옵션이라고 생각합니다.

public void testFirst(){
    Second sec = mock(Second.class);
    when(sec.doSecond()).thenReturn("Stubbed Second");


    First first = new First();
    Field privateField = PrivateObject.class.
        getDeclaredField("second");

    privateField.setAccessible(true);

    privateField.set(first, sec);

    assertEquals("Stubbed Second", first.doSecond());
}

그러나 제어 할 수없는 코드에 대한 테스트는 드물기 때문에 아마도 가능할 수 있습니다 (하지만 외부 라이브러리를 테스트 해야하는 시나리오는 저자가하지 않은 원인을 상상할 수는 있음))


알았다. 아마 당신의 첫 제안과 함께 갈 것입니다.
Anand Hemmige

궁금한 점은 애플리케이션 레벨 또는 패키지 레벨에서 객체 / 메소드를 조롱 할 수있는 방법이나 API가 있다는 것입니다. ? 내가 말하는 것은 위의 예제에서 'Second'객체를 모의 할 때 테스트 수명주기를 통해 사용되는 Second의 모든 인스턴스를 재정의 할 수있는 방법이 있는지 추측합니다. ?
Anand Hemmige

@AnandHemmige는 실제로 두 번째 인스턴스 (생성자)가 더 깔끔합니다. 불필요한 두 번째 인스턴스를 만들지 않기 때문입니다. 당신의 수업은 그렇게 잘 분리됩니다.
soulcheck

10
Mockito는 모의 객체를 개인 변수에 주입 할 수있는 멋진 주석을 제공합니다. 이니셜 라이저에서 두 번째로 주석 달기 @Mock및 첫 번째로 주석 달기 @InjectMocks및 인스턴스화하기. Mockito는 유형에 맞는 개인 필드 설정을 포함하여 Second mock을 First 인스턴스에 주입 할 장소를 찾는 것이 가장 좋습니다.
jhericks

@Mock1.5에 있었다 (아마, 나는 확실하지 않다). 1.8.3 도입 @InjectMocks뿐만 아니라 @Spy@Captor.
jhericks

7

멤버 변수를 변경할 수 없으면 다른 방법으로 powerMockit을 사용하고

Second second = mock(Second.class)
when(second.doSecond()).thenReturn("Stubbed Second");
whenNew(Second.class).withAnyArguments.thenReturn(second);

이제 문제는 new Second를 호출하면 동일한 조롱 된 인스턴스가 반환된다는 것입니다. 그러나 간단한 경우에는 효과가 있습니다.


6

Mockito가 슈퍼 생성자를 호출하지 않기 때문에 개인 값이 설정되지 않은 것과 동일한 문제가있었습니다. 반사를 사용하여 조롱하는 방법을 설명합니다.

먼저, 이러한 리플렉션 메소드를 포함하여 많은 유용한 유틸리티를 포함하는 TestUtils 클래스를 작성했습니다. 리플렉션 액세스는 매번 구현하기에 약간 힘듭니다. 어떤 이유로 든 모의 패키지가 없으며 포함하도록 초대되지 않은 프로젝트에서 코드를 테스트하기 위해 이러한 방법을 만들었습니다.

public class TestUtils {
    // get a static class value
    public static Object reflectValue(Class<?> classToReflect, String fieldNameValueToFetch) {
        try {
            Field reflectField  = reflectField(classToReflect, fieldNameValueToFetch);
            reflectField.setAccessible(true);
            Object reflectValue = reflectField.get(classToReflect);
            return reflectValue;
        } catch (Exception e) {
            fail("Failed to reflect "+fieldNameValueToFetch);
        }
        return null;
    }
    // get an instance value
    public static Object reflectValue(Object objToReflect, String fieldNameValueToFetch) {
        try {
            Field reflectField  = reflectField(objToReflect.getClass(), fieldNameValueToFetch);
            Object reflectValue = reflectField.get(objToReflect);
            return reflectValue;
        } catch (Exception e) {
            fail("Failed to reflect "+fieldNameValueToFetch);
        }
        return null;
    }
    // find a field in the class tree
    public static Field reflectField(Class<?> classToReflect, String fieldNameValueToFetch) {
        try {
            Field reflectField = null;
            Class<?> classForReflect = classToReflect;
            do {
                try {
                    reflectField = classForReflect.getDeclaredField(fieldNameValueToFetch);
                } catch (NoSuchFieldException e) {
                    classForReflect = classForReflect.getSuperclass();
                }
            } while (reflectField==null || classForReflect==null);
            reflectField.setAccessible(true);
            return reflectField;
        } catch (Exception e) {
            fail("Failed to reflect "+fieldNameValueToFetch +" from "+ classToReflect);
        }
        return null;
    }
    // set a value with no setter
    public static void refectSetValue(Object objToReflect, String fieldNameToSet, Object valueToSet) {
        try {
            Field reflectField  = reflectField(objToReflect.getClass(), fieldNameToSet);
            reflectField.set(objToReflect, valueToSet);
        } catch (Exception e) {
            fail("Failed to reflectively set "+ fieldNameToSet +"="+ valueToSet);
        }
    }

}

그런 다음 이와 같은 개인 변수로 클래스를 테스트 할 수 있습니다. 이것은 제어 할 수없는 클래스 트리 깊이를 조롱하는 데 유용합니다.

@Test
public void testWithRectiveMock() throws Exception {
    // mock the base class using Mockito
    ClassToMock mock = Mockito.mock(ClassToMock.class);
    TestUtils.refectSetValue(mock, "privateVariable", "newValue");
    // and this does not prevent normal mocking
    Mockito.when(mock.somthingElse()).thenReturn("anotherThing");
    // ... then do your asserts
}

페이지의 실제 프로젝트에서 코드를 수정했습니다. 컴파일 문제가있을 수 있습니다. 나는 당신이 일반적인 생각을 얻는다고 생각합니다. 유용하다고 생각되면 코드를 잡고 사용하십시오.


실제 사용 사례로 코드를 설명해 주시겠습니까? 퍼블릭 클래스 tobeMocker () {private ClassObject classObject; } 여기서 classObject는 교체 할 객체와 같습니다.
재스퍼 Lankhorst

귀하의 예에서 ToBeMocker instance = new ToBeMocker (); and ClassObject someNewInstance = new ClassObject () {@Override // 외부 의존성 같은 것}; TestUtils.refelctSetValue (인스턴스, "classObject", someNewInstance); 조롱을 위해 재정의하려는 항목을 파악해야합니다. 데이터베이스가 있고이 재정의는 값을 반환하므로 선택할 필요가 없습니다. 가장 최근에는 실제로 메시지를 처리하고 싶지 않지만 메시지를 수신하기를 원하는 서비스 버스가있었습니다. 따라서 개인 버스 인스턴스를 이런 식으로 설정했습니다.
dave

해당 주석에 서식이 있다고 상상해야합니다. 제거되었습니다. 또한 개인 액세스를 잠그기 때문에 Java 9에서는 작동하지 않습니다. 공식 릴리스가 있고 실제 범위에서 작업 할 수있게되면 다른 구조를 사용해야합니다.
dave

1

많은 사람들이 이미 코드를 다시 테스트하여 더 테스트 가능하게 만들 것을 권고했습니다. 좋은 조언이며 일반적으로 내가 제안하려는 것보다 간단합니다.

더 테스트 가능하도록 코드를 변경할 수없는 경우 PowerMock : https://code.google.com/p/powermock/

PowerMock은 Mockito를 확장하여 (새로운 모의 프레임 워크를 배울 필요가 없음) 추가 기능을 제공합니다. 여기에는 생성자가 모형을 반환하도록하는 기능이 포함됩니다. 강력하지만 약간 복잡하므로 신중하게 사용하십시오.

다른 Mock 러너를 사용합니다. 그리고 생성자를 호출 할 클래스를 준비해야합니다. (이것은 일반적인 문제입니다-생성 된 클래스가 아닌 생성자를 호출하는 클래스를 준비하십시오)

@RunWith(PowerMockRunner.class)
@PrepareForTest({First.class})

그런 다음 테스트 설정에서 whenNew 메소드를 사용하여 생성자가 모형을 반환하도록 할 수 있습니다

whenNew(Second.class).withAnyArguments().thenReturn(mock(Second.class));

0

예, 다음 테스트 쇼 (개발 한 JMockit mocking API로 작성 됨)와 같이 수행 할 수 있습니다.

@Test
public void testFirst(@Mocked final Second sec) {
    new NonStrictExpectations() {{ sec.doSecond(); result = "Stubbed Second"; }};

    First first = new First();
    assertEquals("Stubbed Second", first.doSecond());
}

그러나 Mockito를 사용하면 이러한 테스트를 작성할 수 없습니다. 이것은 Mockito에서 조롱이 구현되는 방식 때문인데, 조롱 할 클래스의 서브 클래스가 생성됩니다. 이 "mock"서브 클래스의 인스턴스 만이 모의 동작을 가질 수 있으므로 테스트 된 코드가 다른 인스턴스 대신 사용하도록해야합니다.


3
문제는 JMockit이 Mockito보다 나은지 여부가 아니라 Mockito에서 어떻게 수행하는지였습니다. 경쟁에서 벗어날 수있는 기회를 찾는 대신 더 나은 제품을 만들어 내십시오!
TheZuck

8
원래 포스터는 그가 Mockito를 사용하고 있다고 말합니다. Mockito가 고정적이고 까다로운 요구 사항임을 암시하므로 JMockit 이이 상황을 처리 할 수 ​​있다는 힌트는 그다지 적합하지 않습니다.
Bombe

0

mockito에서 Spring의 ReflectionTestUtils에 대한 대안을 원한다면

Whitebox.setInternalState(first, "second", sec);

스택 오버플로에 오신 것을 환영합니다! OP의 질문을 제공하는 다른 답변이 있으며 몇 년 전에 게시되었습니다. 답변을 게시 할 때는 특히 오래된 질문에 답변하거나 다른 답변에 댓글을 달 때 새로운 솔루션을 추가하거나 실질적으로 더 나은 설명을 추가해야합니다.
help-info.de
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.