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

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

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

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

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

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



답변

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

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

상속보다 구성을 선호

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

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

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


답변

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

    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();
    }


답변

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
    }
} 


답변

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


답변

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


답변

그 이유는 기본 클래스가 공개되지 않았기 때문에 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());
}


답변

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

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

    void superSave(){
        super.save();
    }
}