Mockito 수퍼 클래스의 메서드 호출 만 모의하는 방법


94

일부 테스트에서 Mockito를 사용하고 있습니다.

다음과 같은 수업이 있습니다.

class BaseService {  
    public void save() {...}  
}

public Childservice extends BaseService {  
    public void save(){  
        //some code  
        super.save();
    }  
}   

의 두 번째 호출 ( super.save) 만 모의하고 싶습니다 ChildService. 첫 번째 호출은 실제 메서드를 호출해야합니다. 그렇게하는 방법이 있습니까?


PowerMockito로이 문제를 해결할 수 있습니까?
javaPlease42

@ javaPlease42 : 예, 가능합니다 : stackoverflow.com/a/23884011/2049986 .
Jacob van Lingen

답변:


57

아니요, Mockito는이를 지원하지 않습니다.

이것은 당신이 찾고있는 대답이 아닐 수도 있지만, 당신이보고있는 것은 디자인 원칙을 적용하지 않는 증상입니다.

상속보다 구성을 선호

수퍼 클래스를 확장하는 대신 전략을 추출하면 문제가 사라집니다.

그러나 코드를 변경할 수는 없지만 어쨌든 테스트해야하는데이 어색한 방식으로 여전히 희망이 있습니다. 일부 AOP 도구 (예 : AspectJ)를 사용하면 코드를 수퍼 클래스 메서드로 짜고 실행을 완전히 피할 수 있습니다 (yuck). 프록시를 사용하는 경우 작동하지 않으며 바이트 코드 수정 (로드 시간 위빙 또는 컴파일 시간 위빙)을 사용해야합니다. PowerMock 및 PowerMockito와 같이 이러한 유형의 트릭을 지원하는 모의 프레임 워크도 있습니다.

리팩토링을하는 것이 좋지만 이것이 옵션이 아니라면 심각한 해킹 재미를 느끼는 것입니다.


5
LSP 위반이 보이지 않습니다. 나는 대략 OP와 동일한 설정을 가지고있다 : findAll () 메서드가있는 기본 DAO 클래스와 super.findAll ()을 호출 한 다음 결과를 정렬하여 기본 메서드를 재정의하는 하위 클래스 DAO. 서브 클래스는 수퍼 클래스를 허용하는 모든 컨텍스트로 대체 가능합니다. 내가 당신의 의미를 오해하고 있습니까?

1
LSP 설명을 제거하겠습니다 (답변에 가치를 더하지 않음).
iwein

예, 상속은 짜증나고, 제가 가지고있는 어리석은 프레임 워크는 상속을 유일한 옵션으로하여 설계되었습니다.
Sridhar Sarnobat

슈퍼 클래스를 재 설계 할 수 없다고 가정하면 //some codes코드를 개별적으로 테스트 할 수있는 메서드로 추출 할 수 있습니다.
Phasmal

1
알겠습니다. 내가 이것을 찾을 때 해결하려고했던 것과는 다른 문제이지만, 적어도 그 오해로 인해 기본 클래스에서 모의 ​​호출에 대한 내 문제가 해결되었습니다 (실제로 무시하지 않습니다).
Guillaume Perrot 2017

86

정말로 리팩토링을 선택할 수 없다면 슈퍼 메서드 호출에서 모든 것을 모의 / 스텁 할 수 있습니다.

    class BaseService {

        public void validate(){
            fail(" I must not be called");
        }

        public void save(){
            //Save method of super will still be called.
            validate();
        }
    }

    class ChildService extends BaseService{

        public void load(){}

        public void save(){
            super.save();
            load();
        }
    }

    @Test
    public void testSave() {
        ChildService classToTest = Mockito.spy(new ChildService());

        // Prevent/stub logic in super.save()
        Mockito.doNothing().when((BaseService)classToTest).validate();

        // When
        classToTest.save();

        // Then
        verify(classToTest).load();
    }

2
이 코드는 실제로 super.save () 호출 권한을 막지 못하므로 super.save ()에서 많은 작업을 수행하면 이러한 모든 호출을 막아야합니다 ...
iwein

환상적인 솔루션은 아이가 사용하기 위해 수퍼 클래스 메서드에서 모의 ​​값을 반환하고 싶을 때 놀라운 일을합니다. 환상적입니다. 감사합니다.
Gurnard

14
이는 validate가 숨겨져 있거나 save 메소드가 다른 메소드를 호출하는 대신 직접 작업을 수행하지 않는 한 잘 작동합니다. mockito는하지 않습니다 : Mockito.doNothing (). when ((BaseService) spy) .save (); 이것은 'doNothing on the base service save but on childService save :(
tibi

나는 이것을 내에서 작동시킬 수 없다-자식 메서드도 스텁합니다. BaseService그게 왜 관련이 있는지 모르겠지만 추상적입니다.
Sridhar Sarnobat

1
@ Sridhar-Sarnobat 그래 난 똑같은 일을보고 있어요 :( 누구나 스텁 아웃을 얻는 방법을 아는 사람 super.validate()?
stantonk

4

ChildService.save () 메서드의 코드를 다른 메서드로 리팩토링하고 ChildService.save ()를 테스트하는 대신 새 메서드를 테스트하는 것이 좋습니다. 이렇게하면 super 메서드에 대한 불필요한 호출을 방지 할 수 있습니다.

예:

class BaseService {  
    public void save() {...}  
}

public Childservice extends BaseService {  
    public void save(){  
        newMethod();    
        super.save();
    }
    public void newMethod(){
       //some codes
    }
} 

1

수퍼 클래스 메서드를 호출하는 하위 클래스에 패키지 보호 (동일한 패키지의 테스트 클래스 가정) 메서드를 만든 다음 재정의 된 하위 클래스 메서드에서 해당 메서드를 호출합니다. 그런 다음 스파이 패턴을 사용하여 테스트에서이 방법에 대한 기대치를 설정할 수 있습니다. 예쁘지는 않지만 테스트에서 슈퍼 메서드에 대한 모든 기대 설정을 처리하는 것보다 확실히 낫습니다.


상속에 대한 구성이 거의 항상 더 좋지만 때로는 단순히 상속을 사용하는 것이 더 간단하다고 말할 수 있습니까? 자바가 scala 또는 groovy와 같은 더 나은 구성 모델을 통합 할 때까지 항상 그런 경우가 될
Luke

1

iwein 응답에 전적으로 동의하더라도 (

상속보다 구성을 선호하다

), 상속이 자연스러워 보일 때가 있음을 인정하며 단위 테스트를 위해 그것을 깨거나 리팩토링하는 느낌이 들지 않습니다.

그래서 내 제안 :

/**
 * BaseService is now an asbtract class encapsulating 
 * some common logic callable by child implementations
 */
abstract class BaseService {  
    protected void commonSave() {
        // Put your common work here
    }

    abstract void save();
}

public ChildService extends BaseService {  
    public void save() {
        // Put your child specific work here
        // ...

        this.commonSave();
    }  
}

그런 다음 단위 테스트에서 :

    ChildService childSrv = Mockito.mock(ChildService.class, Mockito.CALLS_REAL_METHODS);

    Mockito.doAnswer(new Answer<Void>() {
        @Override
        public Boolean answer(InvocationOnMock invocation)
                throws Throwable {
            // Put your mocked behavior of BaseService.commonSave() here
            return null;
        }
    }).when(childSrv).commonSave();

    childSrv.save();

    Mockito.verify(childSrv, Mockito.times(1)).commonSave();

    // Put any other assertions to check child specific work is done

0

그 이유는 기본 클래스가 공개되지 않았기 때문에 Mockito는 가시성으로 인해이를 가로 챌 수 없으며, 기본 클래스를 공개로 변경하거나 하위 클래스에서 @Override (공개)를 변경하면 Mockito가 올바르게 모의 할 수 있습니다.

public class BaseService{
  public boolean foo(){
    return true;
  }
}

public ChildService extends BaseService{
}

@Test
@Mock ChildService childService;
public void testSave() {
  Mockito.when(childService.foo()).thenReturn(false);

  // When
  assertFalse(childService.foo());
}

8
이것은 요점이 아닙니다. ChildService은 (foo는 오버라이드 (override)) 및 문제 조롱하는 방법입니다 BaseService.foo ()하지만 ChildService.foo ()
아드리안 코스터

0

상속이 타당하다면 가장 쉬운 방법은 슈퍼를 호출하는 새 메서드 (패키지 private ??)를 만들고 (superFindall이라고 부를 수 있음) 실제 인스턴스를 스파이 한 다음 원하는 방식으로 superFindAll () 메서드를 모의하는 것입니다. 부모 클래스 1입니다. 적용 범위 및 가시성 측면에서 완벽한 솔루션은 아니지만 작업을 수행해야하며 적용하기 쉽습니다.

 public Childservice extends BaseService {
    public void save(){
        //some code
        superSave();
    }

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