Mockito로 정적 메소드 조롱


372

java.sql.Connection객체 를 생성하기 위해 팩토리를 작성했습니다 .

public class MySQLDatabaseConnectionFactory implements DatabaseConnectionFactory {

    @Override public Connection getConnection() {
        try {
            return DriverManager.getConnection(...);
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }
    }
}

에 전달 된 매개 변수의 유효성을 검사하고 싶지만 DriverManager.getConnection정적 메서드를 조롱하는 방법을 모르겠습니다. 테스트 케이스에 JUnit 4와 Mockito를 사용하고 있습니다. 이 특정 사용 사례를 모의 / 확인하는 좋은 방법이 있습니까?


1
도움이 되겠습니까? stackoverflow.com/questions/19464975/…
sasankad

5
당신은 desing 하여 mockito와 함께 할 수 없습니다 :)
MariuszS

25
@MariuszS Mockito (또는 EasyMock 또는 jMock)는 의도적으로 모의 static메서드를 지원하지 않지만 우연히 지원하는 것은 아닙니다 . 이 제한 ( final클래스 클래스 / 메서드 또는 new-ed 객체를 지원하지 않음 )은 조롱을 구현하는 데 사용 된 접근 방식의 자연스러운 (그러나 의도하지 않은) 결과로, 조롱 할 유형을 구현 / 확장하는 새로운 클래스가 동적으로 생성됩니다. 다른 조롱 라이브러리는 이러한 제한을 피하는 다른 접근법을 사용합니다. 이것은 .NET 세계에서도 발생했습니다.
Rogério

2
@ Rogério 설명해 주셔서 감사합니다. github.com/mockito/mockito/wiki/FAQ 정적 메소드를 조롱 할 수 있습니까? Mockito는 이해 및 변경이 어려운 정적 인 절차 코드보다 객체 지향 및 종속성 주입을 선호합니다. 몇 가지있다 디자인 이 제한 뒤에 너무 :)
MariuszS

17
@MariuszS 필자는이 도구가 정당한 정당성을 제공하지 않고 (쉽게) 제거 할 수없는 한계가 있음을 인정하는 대신 정당한 사용 사례를 기각하려는 시도로 읽었습니다. BTW, 여기 에 반대 의견에 대한 토론 이 있습니다.
Rogério

답변:


350

Mockito 위에 PowerMockito 를 사용하십시오 .

예제 코드 :

@RunWith(PowerMockRunner.class)
@PrepareForTest(DriverManager.class)
public class Mocker {

    @Test
    public void shouldVerifyParameters() throws Exception {

        //given
        PowerMockito.mockStatic(DriverManager.class);
        BDDMockito.given(DriverManager.getConnection(...)).willReturn(...);

        //when
        sut.execute(); // System Under Test (sut)

        //then
        PowerMockito.verifyStatic();
        DriverManager.getConnection(...);

    }

추가 정보:


4
이것은 이론적으로 작동하지만 실제로
어려움을 겪고

38
불행히도 이것의 큰 단점은 PowerMockRunner가 필요하다는 것입니다.
Innokenty

18
sut.execute ()? 방법?
TejjD

4
테스트중인 시스템, DriverManager의 모의가 필요한 클래스입니다. kaczanowscy.pl/tomek/2011-01/testing-basics-sut-and-docs
MariuszS

8
참고로, 이미 JUnit4를 사용하고 있다면 @RunWith(PowerMockRunner.class)그 아래에서 할 수 있습니다 @PowerMockRunnerDelegate(JUnit4.class).
EM-Creations 12

71

사용하지 않는 정적 메소드를 피하기위한 일반적인 전략은 랩핑 된 오브젝트를 작성하고 랩퍼 오브젝트를 대신 사용하는 것입니다.

랩퍼 오브젝트는 실제 정적 클래스의 외관이되므로 테스트하지 않습니다.

래퍼 객체는 다음과 같습니다.

public class Slf4jMdcWrapper {
    public static final Slf4jMdcWrapper SINGLETON = new Slf4jMdcWrapper();

    public String myApisToTheSaticMethodsInSlf4jMdcStaticUtilityClass() {
        return MDC.getWhateverIWant();
    }
}

마지막으로 테스트중인 클래스는 예를 들어 실제 사용을위한 기본 생성자를 가짐으로써이 단일 객체를 사용할 수 있습니다.

public class SomeClassUnderTest {
    final Slf4jMdcWrapper myMockableObject;

    /** constructor used by CDI or whatever real life use case */
    public myClassUnderTestContructor() {
        this.myMockableObject = Slf4jMdcWrapper.SINGLETON;
    }

    /** constructor used in tests*/
    myClassUnderTestContructor(Slf4jMdcWrapper myMock) {
        this.myMockableObject = myMock;
    }
}

정적 메서드가있는 클래스를 직접 사용하지 않기 때문에 쉽게 테스트 할 수있는 클래스가 있습니다.

CDI를 사용하고 있고 @Inject 주석을 사용할 수 있으면 훨씬 쉽습니다. Wrapper bean을 @ApplicationScoped로 만들고 공동 작업자로 주입 한 것을 가져 오십시오 (테스트를 위해 지저분한 생성자가 필요하지 않음).


3
정적 호출을 래핑하는 Java 8 "mixin"인터페이스를 자동으로 생성하는 도구를 작성했습니다. github.com/aro-tech/interface-it 생성 된 믹스 인은 다른 인터페이스와 같이 조롱 할 수 있습니다. 인터페이스 테스트의 서브 클래스에서 메소드를 대체 할 수 있습니다.
aro_tech

25

나는 비슷한 문제가 있었다. PowerMock의 mockStatic 설명서@PrepareForTest(TheClassThatContainsStaticMethod.class) 에 따라 다음 과 같이 변경 할 때까지 허용 된 답변이 효과가 없었 습니다 .

그리고 나는 사용할 필요가 없습니다 BDDMockito.

내 수업:

public class SmokeRouteBuilder {
    public static String smokeMessageId() {
        try {
            return InetAddress.getLocalHost().getHostAddress();
        } catch (UnknownHostException e) {
            log.error("Exception occurred while fetching localhost address", e);
            return UUID.randomUUID().toString();
        }
    }
}

내 테스트 수업 :

@RunWith(PowerMockRunner.class)
@PrepareForTest(SmokeRouteBuilder.class)
public class SmokeRouteBuilderTest {
    @Test
    public void testSmokeMessageId_exception() throws UnknownHostException {
        UUID id = UUID.randomUUID();

        mockStatic(InetAddress.class);
        mockStatic(UUID.class);
        when(InetAddress.getLocalHost()).thenThrow(UnknownHostException.class);
        when(UUID.randomUUID()).thenReturn(id);

        assertEquals(id.toString(), SmokeRouteBuilder.smokeMessageId());
    }
}

? .mockStatic 및 .when 현재의 JUnit 4 알아낼 수 없음
테디

작동하지 않는 것처럼 보이는 PowerMock.mockStatic & Mockito.
Teddy

나중에 이것을 보는 사람은 저에게 PowerMockito.mockStatic (StaticClass.class)를 입력해야했습니다.
사상가

powermock-api-mockito maven arterfact를 포함해야합니다.
PeterS

23

앞에서 언급했듯이 mockito를 사용하여 정적 메서드를 조롱 할 수 없습니다.

테스트 프레임 워크 변경이 옵션이 아닌 경우 다음을 수행 할 수 있습니다.

DriverManager에 대한 인터페이스를 생성하고이 인터페이스를 조롱 한 후 일종의 의존성 주입을 통해 주입하고 해당 모의를 확인하십시오.


7

관찰 : 정적 엔티티 내에서 정적 메소드를 호출 할 때 @PrepareForTest에서 클래스를 변경해야합니다.

예를 들어 :

securityAlgo = MessageDigest.getInstance(SECURITY_ALGORITHM);

위의 코드에서 MessageDigest 클래스를 조롱 해야하는 경우

@PrepareForTest(MessageDigest.class)

아래와 같은 것이 있다면 :

public class CustomObjectRule {

    object = DatatypeConverter.printHexBinary(MessageDigest.getInstance(SECURITY_ALGORITHM)
             .digest(message.getBytes(ENCODING)));

}

그런 다음이 코드가있는 클래스를 준비해야합니다.

@PrepareForTest(CustomObjectRule.class)

그런 다음 방법을 조롱하십시오.

PowerMockito.mockStatic(MessageDigest.class);
PowerMockito.when(MessageDigest.getInstance(Mockito.anyString()))
      .thenThrow(new RuntimeException());

정적 클래스가 조롱하지 않은 이유를 알아 내려고 벽에 머리를 두드리고있었습니다. 인터 웹의 모든 자습서에서 ONE은 기본 사용 사례 이상으로 들어갔을 것입니다.
SoftwareSavant

6

약간의 리팩토링으로 할 수 있습니다.

public class MySQLDatabaseConnectionFactory implements DatabaseConnectionFactory {

    @Override public Connection getConnection() {
        try {
            return _getConnection(...some params...);
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }
    }

    //method to forward parameters, enabling mocking, extension, etc
    Connection _getConnection(...some params...) throws SQLException {
        return DriverManager.getConnection(...some params...);
    }
}

그런 다음 클래스 MySQLDatabaseConnectionFactory를 확장하여 조롱 된 연결을 반환하고 매개 변수에 대한 어설 션을 수행 할 수 있습니다 .

확장 클래스는 동일한 패키지에있는 경우 테스트 케이스 내에 상주 할 수 있습니다 (권장합니다)

public class MockedConnectionFactory extends MySQLDatabaseConnectionFactory {

    Connection _getConnection(...some params...) throws SQLException {
        if (some param != something) throw new InvalidParameterException();

        //consider mocking some methods with when(yourMock.something()).thenReturn(value)
        return Mockito.mock(Connection.class);
    }
}

6

정적 방법을 조롱하려면 https://github.com/powermock/powermock/wiki/MockStatic 에서 Powermock을 사용해야합니다 . Mockito 이 기능을 제공하지 않습니다 .

mockito에 대한 좋은 기사를 읽을 수 있습니다 : http://refcardz.dzone.com/refcardz/mockito


2
웹 사이트에 연결하지 마십시오. 답변에는 실제 사용 가능한 답변이 포함되어야합니다. 사이트가 다운되거나 변경되면 답변이 더 이상 유효하지 않습니다.
the_new_mr

6

Mockito는 정적 메소드를 캡처 할 수 없지만 Mockito 2.14.0 부터 정적 메소드의 호출 인스턴스를 작성하여이를 시뮬레이션 할 수 있습니다.

예 ( 테스트 에서 발췌 ) :

public class StaticMockingExperimentTest extends TestBase {

    Foo mock = Mockito.mock(Foo.class);
    MockHandler handler = Mockito.mockingDetails(mock).getMockHandler();
    Method staticMethod;
    InvocationFactory.RealMethodBehavior realMethod = new InvocationFactory.RealMethodBehavior() {
        @Override
        public Object call() throws Throwable {
            return null;
        }
    };

    @Before
    public void before() throws Throwable {
        staticMethod = Foo.class.getDeclaredMethod("staticMethod", String.class);
    }

    @Test
    public void verify_static_method() throws Throwable {
        //register staticMethod call on mock
        Invocation invocation = Mockito.framework().getInvocationFactory().createInvocation(mock, withSettings().build(Foo.class), staticMethod, realMethod,
                "some arg");
        handler.handle(invocation);

        //verify staticMethod on mock
        //Mockito cannot capture static methods so we will simulate this scenario in 3 steps:
        //1. Call standard 'verify' method. Internally, it will add verificationMode to the thread local state.
        //  Effectively, we indicate to Mockito that right now we are about to verify a method call on this mock.
        verify(mock);
        //2. Create the invocation instance using the new public API
        //  Mockito cannot capture static methods but we can create an invocation instance of that static invocation
        Invocation verification = Mockito.framework().getInvocationFactory().createInvocation(mock, withSettings().build(Foo.class), staticMethod, realMethod,
                "some arg");
        //3. Make Mockito handle the static method invocation
        //  Mockito will find verification mode in thread local state and will try verify the invocation
        handler.handle(verification);

        //verify zero times, method with different argument
        verify(mock, times(0));
        Invocation differentArg = Mockito.framework().getInvocationFactory().createInvocation(mock, withSettings().build(Foo.class), staticMethod, realMethod,
                "different arg");
        handler.handle(differentArg);
    }

    @Test
    public void stubbing_static_method() throws Throwable {
        //register staticMethod call on mock
        Invocation invocation = Mockito.framework().getInvocationFactory().createInvocation(mock, withSettings().build(Foo.class), staticMethod, realMethod,
                "foo");
        handler.handle(invocation);

        //register stubbing
        when(null).thenReturn("hey");

        //validate stubbed return value
        assertEquals("hey", handler.handle(invocation));
        assertEquals("hey", handler.handle(invocation));

        //default null value is returned if invoked with different argument
        Invocation differentArg = Mockito.framework().getInvocationFactory().createInvocation(mock, withSettings().build(Foo.class), staticMethod, realMethod,
                "different arg");
        assertEquals(null, handler.handle(differentArg));
    }

    static class Foo {

        private final String arg;

        public Foo(String arg) {
            this.arg = arg;
        }

        public static String staticMethod(String arg) {
            return "";
        }

        @Override
        public String toString() {
            return "foo:" + arg;
        }
    }
}

그들의 목표는 정적 조롱을 직접 지원하는 것이 아니라 퍼블릭 API를 개선하여 Powermockito 와 같은 다른 라이브러리 가 내부 API에 의존하거나 Mockito 코드를 직접 복제 할 필요가 없도록하는 것입니다. ( 소스 )

면책 조항 : Mockito 팀은 지옥으로가는 길에 정적 방법이 포장되어 있다고 생각합니다. 그러나 Mockito의 임무는 정적 메소드로부터 코드를 보호하는 것이 아닙니다. 팀이 정적 조롱을하는 것을 좋아하지 않는다면 조직에서 Powermockito 사용을 중단하십시오. Mockito는 Java 테스트 작성 방법에 대한 의견을 가진 툴킷으로 진화해야합니다 (예 : 정적을 조롱하지 마십시오 !!!). 그러나 Mockito는 독단적이 아닙니다. 정적 조롱과 같은 권장되지 않는 사용 사례를 차단하고 싶지 않습니다. 우리 일이 아니에요



1

이 메소드는 정적이므로 사용하는 데 필요한 모든 것이 이미 있으므로 모의 목적을 상실합니다. 정적 메서드를 조롱하는 것은 나쁜 습관으로 간주됩니다.

그렇게하려고하면 테스트를 수행하려는 방식에 문제가 있음을 의미합니다.

물론 PowerMockito 또는이를 수행 할 수있는 다른 프레임 워크를 사용할 수 있지만 접근 방식을 다시 생각하십시오.

예를 들어, 정적 메서드가 대신 사용하는 객체를 조롱 / 제공하려고합니다.


0

JMockit 프레임 워크 사용 . 그것은 나를 위해 일했다. DBConenction.getConnection () 메소드를 조롱하기 위해 명령문을 작성할 필요는 없습니다. 아래 코드만으로 충분합니다.

아래 @Mock은 mockit.Mock 패키지입니다.

Connection jdbcConnection = Mockito.mock(Connection.class);

MockUp<DBConnection> mockUp = new MockUp<DBConnection>() {

            DBConnection singleton = new DBConnection();

            @Mock
            public DBConnection getInstance() { 
                return singleton;
            }

            @Mock
            public Connection getConnection() {
                return jdbcConnection;
            }
         };
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.