mockito로 최종 수업을 조롱하는 방법


218

나는 마지막 수업을 가지고 있습니다 :

public final class RainOnTrees{

   public void startRain(){

        // some code here
   }
}

이 클래스를 다음과 같은 다른 클래스에서 사용하고 있습니다.

public class Seasons{

   RainOnTrees rain = new RainOnTrees();

   public void findSeasonAndRain(){

        rain.startRain();

    }
}

JUnit 테스트 클래스 Seasons.java에서 RainOnTrees클래스 를 조롱하고 싶습니다 . Mockito로 어떻게 할 수 있습니까?


9
Mockito는 허용하지 않지만 PowerMock은 허용합니다.
fge

1
Mockito 2.x부터 Mockito는 이제 최종 클래스와 메소드의 조롱을 지원합니다.
Kent

답변:


155

최종 / 정적 클래스 / 메소드를 조롱하는 것은 Mockito v2에서만 가능합니다.

gradle 파일에 이것을 추가하십시오 :

testImplementation 'org.mockito:mockito-inline:2.13.0'

Mockito FAQMockito v1에서는 불가능합니다 .

Mockito의 한계는 무엇입니까

  • Java 1.5 이상이 필요합니다

  • 최종 수업을 조롱 할 수 없습니다

...


2
Scala에서 sbt 수정과 함께 작동하지 않았습니다.
micseydel

2
이것은 충분하지 않았습니다. 나는 또한 SRC / 테스트 / 자원 / mockito-확장을 만들어야했습니다 / org.mockito.plugins.MockMaker에 따라 거기에 "모의 메이커 인라인"와 baeldung.com/mockito-final
micseydel

204

Mockito 2는 이제 최종 클래스와 메소드를 지원 합니다!

그러나 현재로서는 "인큐베이팅"기능입니다. Mockito 2의 새로운 기능에 설명 된대로 활성화하려면 몇 가지 단계가 필요합니다 .

최종 클래스와 메서드를 조롱하는 것은 잠복 하고 옵트 인 기능입니다. Java 에이전트 인스 트루먼 테이션과 서브 클래 싱의 조합을 사용하여 이러한 유형의 모형을 구현할 수 있습니다. 이 기능은 현재 메커니즘과 다르게 작동하며 다른 제한 사항이 있으며 경험과 사용자 피드백을 수집하려면이 기능을 명시 적으로 활성화해야 사용할 수 있습니다. src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker단일 행을 포함하는 파일을 작성하여 mockito 확장 메커니즘을 통해 수행 할 수 있습니다 .

mock-maker-inline

이 파일을 만든 후 Mockito는이 새 엔진을 자동으로 사용하며 다음을 수행 할 수 있습니다.

 final class FinalClass {
   final String finalMethod() { return "something"; }
 }

 FinalClass concrete = new FinalClass(); 

 FinalClass mock = mock(FinalClass.class);
 given(mock.finalMethod()).willReturn("not anymore");

 assertThat(mock.finalMethod()).isNotEqualTo(concrete.finalMethod());

이후 이정표에서 팀은이 기능을 사용하는 프로그래밍 방식을 제공합니다. 우리는 모든 불가능한 시나리오를 식별하고 지원할 것입니다. 지켜봐 주시고이 기능에 대한 귀하의 의견을 알려주십시오!


14
난 여전히 오류가 발생합니다 : 캔 모의하지 / 스파이 클래스 android.content.ComponentName Mockito 수 없습니다 모의 / 스파이 때문에 - final 클래스
IgorGanapolsky

3
org.mockito.plugins.MockMaker파일을 올바른 폴더에 넣으십시오 .
WindRider

7
또한 위에서 언급 한 후에도 오류가 발생합니다. Mockito는 다음과 같은 이유로 조롱 / 스파이 할 수 없습니다 :-final class
rcde0

8
@vCillusion이 답변은 PowerMock과 관련이 없습니다.
Line

6
나는이 지시 사항을 따랐지만 여전히이 일을 할 수 없었습니다. 다른 사람이해야합니까?
프랑코

43

혼자서 할 수 없으므로 Mockito로 최종 수업을 조롱 할 수 없습니다.

내가하는 일은 최종 클래스를 포장하고 대리자로 사용하기 위해 최종 클래스가 아닌 클래스를 만드는 것입니다. 이것의 예는 TwitterFactory클래스이며, 이것은 내 조롱 가능한 클래스입니다.

public class TwitterFactory {

    private final twitter4j.TwitterFactory factory;

    public TwitterFactory() {
        factory = new twitter4j.TwitterFactory();
    }

    public Twitter getInstance(User user) {
        return factory.getInstance(accessToken(user));
    }

    private AccessToken accessToken(User user) {
        return new AccessToken(user.getAccessToken(), user.getAccessTokenSecret());
    }

    public Twitter getInstance() {
        return factory.getInstance();
    }
}

단점은 많은 상용구 코드가 있다는 것입니다. 장점은 애플리케이션 비즈니스와 관련된 몇 가지 메소드를 추가 할 수 있다는 것입니다 (위의 경우 accessToken 대신 사용자를 가져 오는 getInstance와 같이).

귀하의 경우 최종 RainOnTrees클래스에 위임하는 최종 클래스가 아닌 클래스를 만듭니다 . 또는 최종이 아닌 것으로 만들 수 있다면 더 좋습니다.


6
+1. 원하는 경우 롬복과 같은 것을 사용 @Delegate하여 많은 상용구를 처리 할 수 있습니다 .
ruakh

2
@luigi Junit에 대한 코드 스 니펫을 예제로 추가 할 수 있습니다. 마지막 수업을 위해 Wrapper를 만들려고했지만 테스트 할 수 없었습니다.
Incredible

31

gradle 파일에 이것을 추가하십시오 :

testImplementation 'org.mockito:mockito-inline:2.13.0'

이것은 최종 클래스와 함께 mockito를 작동시키는 구성입니다.


1
아마도 "testCompile"대신 "testImplementation"을 사용해야합니다. Gradle은 더 이상 "testCompile"을 좋아하지 않습니다.
jwehrle

좋은 의견 감사합니다! testImplementation으로 편집되었습니다. 원래 코멘트 : testCompile 'org.mockito : mockito 인라인 : 2.13.0'
BennyP

2
Linux / OpenJDK 1.8에서 실행할 때 오류가 발생합니다.org.mockito.exceptions.base.MockitoInitializationException: Could not initialize inline Byte Buddy mock maker. (This mock maker is not supported on Android.)
naXa

naXa

23

Powermock을 사용하십시오. 이 링크는이를 수행하는 방법을 보여줍니다. https://github.com/jayway/powermock/wiki/MockFinal


30
PowerMock은 "처방전"기반으로 만 나가야하는 의약품 중 하나와 같습니다. 의미에서 : PowerMock 에 많은 문제 가 있음을 분명히해야 합니다. 그리고 그것을 사용하는 것이 최후의 최후의 수단과 같습니다. 최대한 많이 피해야합니다.
GhostCat

1
왜 그런 말을 해?
PragmaticProgrammer

나는 Powermock공식적으로 확인 된 내 범위를 늘리기 위해 최종 클래스와 정적 메소드를 조롱하는 데 사용 했습니다 Sonarqube. SonarQube 이후 적용 범위는 0 %였습니다. 이유는 무엇이든 Powermock을 사용하는 클래스를 인식하지 못하기 때문입니다. 나는 스레드와 온라인에서 그것을 깨닫기 위해 꽤 오랜 시간이 걸렸다. 따라서 Powermock에주의해야 할 이유 중 하나 일 뿐이며 사용하지 않을 수도 있습니다.
amer

16

후속 조치를 취하십시오. 이 줄을 gradle 파일에 추가하십시오 :

testCompile group: 'org.mockito', name: 'mockito-inline', version: '2.8.9'

나는 mockito-core와 mockito-all의 다양한 버전을 시도했습니다. 둘 다 작동하지 않습니다.


1
이것에 덧붙여, 내가 관찰 한 가지는 당신이 mockito와 함께 Powermock을 사용한다면; 그런 다음 'src / test / resources / mockito-extensions / org.mockito.plugins.MockMaker'에 mockmaker 플러그인 파일을 추가하는 것은 최종 클래스를 조롱하는 데 유용하지 않습니다. 대신, Michael_Zhang이 언급 한 것과 같은 종속성을 추가하면 최종 클래스를 조롱하는 문제가 해결됩니다. 또한 Mockito1 대신 Mockito 2를 사용하고 있는지 확인하십시오.
dishooom

12

final다른 클래스의 확장을 막고 싶기 때문에 당신이 만든 것 같아요 RainOnTrees. 같이 효과적인 자바 제안 (항목 15)을하지 않고 확장을위한 클래스 가까이를 유지하는 또 다른 방법이있다 final:

  1. final키워드를 제거하십시오 .

  2. 생성자 만들기 private . super생성자 를 호출 할 수 없으므로 클래스를 확장 할 수 없습니다 .

  3. 클래스를 인스턴스화하는 정적 팩토리 메소드를 작성하십시오.

    // No more final keyword here.
    public class RainOnTrees {
    
        public static RainOnTrees newInstance() {
            return new RainOnTrees();
        }
    
    
        private RainOnTrees() {
            // Private constructor.
        }
    
        public void startRain() {
    
            // some code here
        }
    }

이 전략을 사용하면 Mockito를 사용하고 상용구 코드를 거의 사용하지 않고 수업을 계속 닫을 수 있습니다.


1
mockito 2를 사용하여 조롱 할 수있는 최종 방법에는 작동하지 않습니다.
Łukasz Rzeszotarski

11

나는 같은 문제가 있었다. 내가 조롱하려는 클래스는 간단한 클래스이므로 간단히 인스턴스를 만들어 반환했습니다.


2
물론, 왜 간단한 수업을 조롱해야합니까? 조롱은 다른 서비스, 엔진, 데이터 클래스 등 '비싼'상호 작용을위한 것입니다.
StripLight

3
해당 인스턴스를 만들면 나중에 Mockito.verify 메서드를 적용 할 수 없습니다. 모의의 주요 용도는 그 방법 중 일부를 테스트 할 수있는 것입니다.
riroo

6

이것을 시도하십시오 :

Mockito.mock(SomeMockableType.class,AdditionalAnswers.delegatesTo(someInstanceThatIsNotMockableOrSpyable));

그것은 나를 위해 일했다. "SomeMockableType.class"는 조롱하거나 스파이하려는 부모 클래스이며 someInstanceThatIsNotMockableOrSpyable은 조롱하거나 스파이하려는 실제 클래스입니다.

자세한 내용은 여기 를 참조 하십시오


3
델리게이트는 네이티브 스파이 조롱과는 매우 다릅니다. 네이티브 mockito 스파이에서, 인스턴스의 "this"는 스파이 자체를 참조합니다 (서브 클래스를 사용하기 때문입니다). 그러나 델리게이트에서 "this"는 실제 인스턴스 someInstanceThatIsNotMockableOrSpyable이됩니다. 스파이가 아닙니다. 따라서, 자기 호출 기능에 대해 반환 / 확인을 수행 할 방법이 없습니다.
Dennis C

1
예를 들어 줄 수 있습니까?
Vishwa Ratna

5

경우에 따라 적용 할 수있는 또 다른 해결 방법은 해당 최종 클래스에서 구현되는 인터페이스를 만들고 구체적인 클래스 대신 인터페이스를 사용하도록 코드를 변경 한 다음 인터페이스를 조롱하는 것입니다. 이를 통해 계약 (인터페이스)을 구현 (최종 클래스)과 분리 할 수 ​​있습니다. 물론 원하는 것이 실제로 최종 클래스에 바인딩하는 경우 적용되지 않습니다.


5

실제로 스파이를 위해 사용하는 한 가지 방법이 있습니다. 두 가지 전제 조건이 충족되는 경우에만 작동합니다.

  1. 어떤 종류의 DI를 사용하여 최종 클래스의 인스턴스를 주입합니다.
  2. 최종 클래스는 인터페이스를 구현

Effective Java의 항목 16을 기억하십시오 . 래퍼 (최종 아님)를 만들고 모든 호출을 final 클래스의 인스턴스로 전달할 수 있습니다.

public final class RainOnTrees implement IRainOnTrees {
    @Override public void startRain() { // some code here }
}

public class RainOnTreesWrapper implement IRainOnTrees {
    private IRainOnTrees delegate;
    public RainOnTreesWrapper(IRainOnTrees delegate) {this.delegate = delegate;}
    @Override public void startRain() { delegate.startRain(); }
}

이제 당신은 마지막 수업을 조롱 할 수있을뿐만 아니라 그것에 대해 감시 할 수도 있습니다 :

public class Seasons{
    RainOnTrees rain;
    public Seasons(IRainOnTrees rain) { this.rain = rain; };
    public void findSeasonAndRain(){
        rain.startRain();
   }
}

IRainOnTrees rain = spy(new RainOnTreesWrapper(new RainOnTrees()) // or mock(IRainOnTrees.class)
doNothing().when(rain).startRain();
new Seasons(rain).findSeasonAndRain();

5

Mockito 3 이상에서 나는 같은 문제가 있으며이 링크에서 수정했습니다.

다음 과 같이 Mockito사용한 Mock Final 클래스 및 방법

최종 클래스 및 메소드를 조롱하는 데 Mockito를 사용하려면> 구성해야합니다.

프로젝트의 src / test / resources / mockito-extensions 디렉토리에 org.mockito.plugins.MockMaker라는 텍스트 파일을 추가하고 한 줄의 텍스트를 추가해야합니다.

mock-maker-inline

Mockito는 확장 디렉토리에서 구성 파일이로드 될 때 구성 파일을 확인합니다. 이 파일을 사용하면 최종 메서드 및 클래스를 조롱 할 수 있습니다.


4

Android + Kotlin에서 동일한 문제 (Mockito + Final Class)에 직면 한 사람들을위한 시간 절약 Kotlin 클래스와 마찬가지로 기본적으로 최종 클래스입니다. Architecture 구성 요소가있는 Google Android 샘플 중 하나에서 솔루션을 찾았습니다. 여기에서 선택한 솔루션 : https://github.com/googlesamples/android-architecture-components/blob/master/GithubBrowserSample

다음 주석을 작성하십시오.

/**
 * This annotation allows us to open some classes for mocking purposes while they are final in
 * release builds.
 */
@Target(AnnotationTarget.ANNOTATION_CLASS)
annotation class OpenClass

/**
 * Annotate a class with [OpenForTesting] if you want it to be extendable in debug builds.
 */
@OpenClass
@Target(AnnotationTarget.CLASS)
annotation class OpenForTesting

gradle 파일을 수정하십시오. 여기에서 예를 들어보십시오 : https://github.com/googlesamples/android-architecture-components/blob/master/GithubBrowserSample/app/build.gradle

apply plugin: 'kotlin-allopen'

allOpen {
    // allows mocking for classes w/o directly opening them for release builds
    annotation 'com.android.example.github.testing.OpenClass'
}

이제 모든 클래스에 주석을 달아 테스트를 위해 열 수 있습니다.

@OpenForTesting
class RepoRepository 

이것은 app 수준 build.gradle에서 잘 작동하지만 라이브러리 수준 에서이 작업을 수행하려면 어떻게해야합니까?
Su T T

좀 더 정교하게 할 수 있습니까? 일반적으로 파사드 패턴을 사용하여 라이브러리에 연결하십시오. 이 파사드 클래스를 조롱하여 앱을 테스트하십시오. 이런 식으로 lib 클래스를 조롱 할 필요가 없습니다.
Ozeetee 2016 년

3

Mockito2를 사용하는 경우 최종 클래스 및 메소드의 조롱을 지원하는 새로운 인큐베이팅 기능을 사용하여이 작업을 수행 할 수 있습니다.

요점 :
1. "org.mockito.plugins.MockMaker"라는 이름의 간단한 파일을 만들어 "mockito-extensions"라는 폴더에 넣습니다. 이 폴더는 클래스 경로에서 사용할 수 있어야합니다.
2. 위에서 생성 된 파일의 내용은 아래와 같이 한 줄이어야합니다 :
mock-maker-inline

mockito 확장 메커니즘을 활성화하고이 옵트 인 기능을 사용하려면 위의 두 단계가 필요합니다.

샘플 클래스는 다음과 같습니다.

FinalClass.java

public final class FinalClass {

public final String hello(){
    System.out.println("Final class says Hello!!!");
    return "0";
}

}

Foo.java

public class Foo {

public String executeFinal(FinalClass finalClass){
    return finalClass.hello();
}

}

FooTest.java

public class FooTest {

@Test
public void testFinalClass(){
    // Instantiate the class under test.
    Foo foo = new Foo();

    // Instantiate the external dependency
    FinalClass realFinalClass = new FinalClass();

    // Create mock object for the final class. 
    FinalClass mockedFinalClass = mock(FinalClass.class);

    // Provide stub for mocked object.
    when(mockedFinalClass.hello()).thenReturn("1");

    // assert
    assertEquals("0", foo.executeFinal(realFinalClass));
    assertEquals("1", foo.executeFinal(mockedFinalClass));

}

}

도움이 되길 바랍니다.

여기에 조롱 할 수 없는 완전한 기사가 있습니다 .


여기에 답변을 포함시켜야하며 외부 사이트로 연결되지 않아야합니다. 절차가 길면 개요를 포함시킬 수 있습니다.
rghome

@RunWith (PowerMockRunner.class)를 조롱 할 때 아래 주석을 사용해야합니다. @PrepareForTest ({AFinalClass.class})
vCillusion

1
@vCillusion-내가 보여준 예제는 Mockito2 API를 사용합니다. Mockito2의 옵트 인 기능을 사용하면 Powermock을 사용하지 않고도 최종 클래스를 직접 조롱 할 수 있습니다.
ksl

2

예, 같은 문제입니다. Mockito로 최종 수업을 조롱 할 수는 없습니다. 정확하기 위해 Mockito는 다음을 조롱 / 스파이 할 수 없습니다.

  • 최종 수업
  • 익명 클래스
  • 기본 유형

그러나 래퍼 클래스를 사용하면 큰 비용을 지불해야하므로 PowerMockito를 대신 사용하십시오.


2

더 많은 생각이 필요하다고 생각합니다. 대신 당신은 마지막 수업에서 그의 인터페이스와 모의 인터페이스를 대신 사용합니다.

이를 위해 :

 public class RainOnTrees{

   fun startRain():Observable<Boolean>{

        // some code here
   }
}

더하다

interface iRainOnTrees{
  public void startRain():Observable<Boolean>
}

그리고 당신에게 인터페이스를 조롱 :

 @Before
    fun setUp() {
        rainService= Mockito.mock(iRainOnTrees::class.java)

        `when`(rainService.startRain()).thenReturn(
            just(true).delay(3, TimeUnit.SECONDS)
        )

    }

1

JMockit보십시오 . 많은 예제와 함께 광범위한 문서가 있습니다. 여기에 문제의 해결책이 있습니다 ( Seasons모의 RainOnTrees인스턴스 를 주입 하기 위해 생성자를 추가했습니다 ).

package jmockitexample;

import mockit.Mocked;
import mockit.Verifications;
import mockit.integration.junit4.JMockit;
import org.junit.Test;
import org.junit.runner.RunWith;

@RunWith(JMockit.class)
public class SeasonsTest {

    @Test
    public void shouldStartRain(@Mocked final RainOnTrees rain) {
        Seasons seasons = new Seasons(rain);

        seasons.findSeasonAndRain();

        new Verifications() {{
            rain.startRain();
        }};
    }

    public final class RainOnTrees {
        public void startRain() {
            // some code here
        }

    }

    public class Seasons {

        private final RainOnTrees rain;

        public Seasons(RainOnTrees rain) {
            this.rain = rain;
        }

        public void findSeasonAndRain() {
            rain.startRain();
        }

    }
}

1

RC와 Luigi R. Viggiano가 함께 제공하는 솔루션이 가장 좋습니다.

Mockito 가 의도적으로 최종 클래스를 모의 할 수는 없지만 위임 방식 이 가능합니다 . 장점이 있습니다.

  1. API가 처음에 의도 한 것이라면 클래스를 최종이 아닌 것으로 변경하지 않아도됩니다 (최종 클래스의 장점이 있음 ).
  2. API 주위 에 장식 가능성을 테스트하고 있습니다.

테스트 사례에서는 의도적으로 테스트중인 시스템으로 통화를 전달합니다. 따라서 설계 상 장식은 아무것도 하지 않습니다 .

따라서 테스트를 통해 사용자가 API를 확장하는 대신 장식 만 할 수 있음을 보여줄 수도 있습니다.

더 주관적인 메모 : 프레임 워크를 최소로 유지하는 것을 선호하므로 JUnit과 Mockito가 일반적으로 충분합니다. 사실,이 방법을 제한하면 때로는 나에게도 좋은 리팩토링이 필요합니다.


1

당신은 아래에 단위 테스트를 실행하려고하면 테스트 폴더 하면 최상위 솔루션이 좋습니다. 확장 프로그램을 추가하면됩니다.

그러나 androidtest 폴더에 있는 컨텍스트 또는 활동과 같은 안드로이드 관련 클래스 로 실행 하려면 답 이 있습니다.


1

mockito를 성공적으로 실행하려면 다음과 같은 종속성을 추가하십시오.

testImplementation 'org.mockito : mockito-core :
2.24.5'testImplementation "org.mockito : mockito- 인라인 : 2.24.5"


0

다른 사람들이 말했듯이 이것은 Mockito와 함께 즉시 작동하지 않습니다. 리플렉션을 사용하여 테스트중인 코드에서 사용중인 객체의 특정 필드를 설정하는 것이 좋습니다. 이 작업을 많이하면 라이브러리에서이 기능을 래핑 할 수 있습니다.

한마디로, 최종 마킹 클래스 인 경우 그만하십시오. 확장 (모의)에 대한 정당한 요구를 막기 위해 모든 것이 최종으로 표시된 API로 작업하고 있기 때문에이 질문을 가로 질러 왔으며 개발자가 클래스를 확장 할 필요가 없다고 가정하지 않았기를 바랍니다.


1
공개 API 클래스는 확장을 위해 열려 있어야합니다. 전적으로 동의합니다. 그러나 개인 코드 기반에서는 final기본값이어야합니다.
ErikE

0

우리에게는 코인 테스트에서 mockito-inline을 제외했기 때문입니다. 하나의 gradle 모듈은 실제로 이것을 필요로했으며 릴리스 빌드에서만 실패했습니다 (IDE의 디버그 빌드가 작동했습니다) :-P


0

최종 클래스의 경우 아래를 추가하여 정적 또는 비 정적을 조롱하고 호출하십시오.

1- 이것을 클래스 레벨 @SuppressStatucInitializationFor (value = {패키지가있는 클래스 이름})에 추가하십시오.
2- PowerMockito.mockStatic (classname.class)는 클래스를 모의합니다.
3을 한 다음이 클래스의 메소드를 호출 할 때 when 문을 사용하여 모의 객체를 반환합니다.

즐겨


-5

마지막으로 시도하지는 않았지만 개인용으로 리플렉션을 사용하여 수정자를 제거했습니다! 추가로 확인하면 최종적으로 작동하지 않습니다.


이것은 질문에 대답하지 않았습니다
Sanjit Kumar Mishra
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.