2015-01-09 3 views
4

는이 같은 구조를 가지고 : 예외를 throw하기 위해 void 메소드를 조롱 할 수 있습니까?

public class CacheWrapper { 
    private Map<Object, Object> innerMap; 

    public CacheWrapper() { 
     //initialize the innerMap with an instance for an in-memory cache 
     //that works on external server 
     //current implementation is not relevant for the problem 
     innerMap = ...; 
    } 

    public void putInSharedMemory(Object key, Object value) { 
     innerMap.put(key, value); 
    } 

    public Object getFromSharedMemory(Object key) { 
     return innerMap.get(key); 
    } 
} 

그리고 내 클라이언트 클래스는 (당신은 다음과 같습니다 말할 수) :

public class SomeClient { 
    //Logger here, for exception handling 
    Logger log = ...; 
    private CacheWrapper cacheWrapper; 
    //getter and setter for cacheWrapper... 

    public Entity getEntity(String param) { 
     Entity someEntity = null; 
     try { 
      try { 
       entity = cacheWrapper.getFromSharedMemory(param); 
      } catch (Exception e) { 
       //probably connection failure occurred here 
       log.warn("There was a problem when getting from in-memory " + param + " key.", e); 
      } 
      if (entity == null) { 
       entity = ...; //retrieve it from database 
       //store in in-memory cache 
       try { 
        cacheWrapper.put(param, entity); 
       } catch (Exception e) { 
        //probably connection failure occurred here 
        log.warn("There was a problem when putting in in-memory " + param + " key.", e); 
       } 
      } 
     } catch (Exception e) { 
      logger.error(".......", e); 
     } 
     return entity; 
    } 
} 

내가 SomeClient#getEntity 방법에 대한 단위 테스트를 작성하고있는거야 모든 시나리오를 다룰 수 있습니다. 예를 들어, cacheWrapper에 의해 던진 예외가있는 시나리오를 다루어야합니다. 내가 따르는 접근법은 CacheWrapper 클래스의 모의 객체를 만들고 CacheWrapper 클래스의 메소드를 RuntimeException을 던집니다. SomeClient의 인스턴스에서이 모의 객체를 설정하고 Someclient#getEntity을 테스트합니다. 문제는 void이기 때문에 putInSharedMemory 메서드를 모의하려고 할 때입니다. 나는 이것을하기 위해 많은 방법을 시도했지만 그 중 누구도 일하지 않습니다. 프로젝트의 PowerMock 및 EasyMock에 대한 종속성이 있습니다.

  1. EasyMock.<Void>expect 사용 : 여기

    내 시도이다. 이로 인해 컴파일러 오류가 발생했습니다.
  2. 스터브하려고 시도 CacheWrapper#putInSharedMemory. 일을하지 않았다 때문에이 오류 메시지와 함께 예외를 제기 : 프로젝트에

    java.lang.AssertionError: Unexpected method call putInSharedMemory("foo", [email protected])

  3. 추가 Mockito 의존성이 PowerMockito 클래스의 기능을 활용할 수 있습니다. 그러나 EasyMock과 통합되지 않기 때문에 예외가 발생했습니다.

    java.lang.ClassCastException: org.powermock.api.easymock.internal.invocationcontrol.EasyMockMethodInvocationControl cannot be cast to org.powermock.api.mockito.internal.invocationcontrol.MockitoMethodInvocationControl

여기이 단위 테스트 샘플에 대한 코드이다 : 그것은 제 3 자 라이브러리에서 오기 때문에

@Test 
public void getEntityWithCacheWrapperException() { 
    CacheWrapper cacheWrapper = mockThrowsException(); 
    SomeClient someClient = new SomeClient(); 
    someClient.setCacheWrapper(cacheWrapper); 
    Entity entity = someClient.getEntity(); 
    //here.....................^ 
    //cacheWrapper.putInSharedMemory should throw an exception 

    //start asserting here... 
} 

//... 

public CacheWrapper mockThrowsException() { 
    CacheWrapper cacheWrapper = PowerMock.createMock(CacheWrapper.class); 
    //mocking getFromSharedMemory method 
    //this works like a charm 
    EasyMock.expect(cacheWrapper.getFromSharedMemory(EasyMock.anyObject())) 
     .andThrow(new RuntimeException("This is an intentional Exception")).anyTimes(); 

    //mocking putInSharedMemory method 
    //the pieces of code here were not executed at the same time 
    //instead they were commented and choose one approach after another 

    //attempt 1: compiler exception: <Void> is not applicable for <void> 
    EasyMock.<Void>expect(cacheWrapper.putInSharedMemory(EasyMock.anyObject(), EasyMock.anyObject())) 
     .andThrow(new RuntimeException("This is an intentional Exception")).anyTimes(); 

    //attempt 2: stubbing the method 
    //exception when executing the test: 
    //Unexpected method call putInSharedMemory("foo", [email protected]) 
    Method method = PowerMock.method(CacheWrapper.class, "putInSharedMemory", Object.class, Object.class); 
    PowerMock.stub(method).toThrow(new RuntimeException("Exception on purpose.")); 

    //attempt 3: added dependency to Mockito integrated to PowerMock 
    //bad idea: the mock created by PowerMock.createMock() belongs to EasyMock, not to Mockito 
    //so it breaks when performing the when method 
    //exception: 
    //java.lang.ClassCastException: org.powermock.api.easymock.internal.invocationcontrol.EasyMockMethodInvocationControl 
    //cannot be cast to org.powermock.api.mockito.internal.invocationcontrol.MockitoMethodInvocationControl 
    //at org.powermock.api.mockito.internal.expectation.PowerMockitoStubberImpl.when(PowerMockitoStubberImpl.java:54) 
    PowerMockito.doThrow(new RuntimeException("Exception on purpose.")) 
     .when(cacheWrapper).putInSharedMemory(EasyMock.anyObject(), EasyMock.anyObject()); 

    PowerMock.replay(cacheWrapper); 
    return cacheWrapper; 
} 

내가 CacheWrapper의 구현을 변경할 수 없습니다이 제기 된 예외입니다. 또한 SomeClient#getEntity에서 테스트를 수행 중이므로 EasyMock#getLastCall을 사용할 수 없습니다.

어떻게 이것을 극복 할 수 있습니까? 같은

+0

왜 전혀 PowerMockito 필요합니까? 테스트 된 클래스 중 마지막 클래스는 하나도 없으므로 인스턴스화 된 인스턴스 (sp_can_ 스텁 스파이)를 통해 단지 'spy()'를 사용할 수 있습니다. 또한, .replay()는 어디에서 왔습니까? – fge

+0

@fge 필자는 순수 단위 테스트가 아닌 통합 테스트를 작성하기 때문에 이러한 프레임 워크를 사용하는 데 익숙하지 않습니다. 이'spy()'메소드 나 관련 문서에 대해 더 자세히 설명해 주시겠습니까? –

+0

모두 테스트 코드는 모두 정말 기괴합니다. 당신은 easockock과 (전원) mockito를 사용하는 것처럼 보입니다 ... 어떤 이유가 있습니까? – fge

답변

10

수업 중에 최종 없기 때문에, 당신은 PowerMockito에 의지하지 않고 "순수 mockito"를 사용할 수 있습니다 :

final CacheWrapper wrapper = Mockito.spy(new CacheWrapper()); 

Mockito.doThrow(something) 
    .when(wrapper).putInSharedMemory(Matchers.any(), Matchers.any()); 

참고 "메소드 인수"에 스텁 (stub)은 사실 인수 일치 자입니다. 특정 값을 입력 할 수 있습니다 (특정 메소드에 "둘러싸이지"않은 경우에는 .equals()을 호출합니다). 따라서 서로 다른 주장에 대해 스텁의 동작을 다르게 안내 할 수 있습니다.

또한 어떤 종류의 .replay()과 Mockito가 필요하지 않습니다. 매우 좋습니다!

마지막으로 doCallRealMethod() 수 있습니다. 그 후에, 그것은 당신의 시나리오에 달려 있습니다 ...

(참고 : 받는다는에 마지막 mockito 버전을 사용할 수는 1.10.17 FWIW입니다)

0

사용 expectLastCall :

cacheWrapper.putInSharedMemory(EasyMock.anyObject(), EasyMock.anyObject()) 
    EasyMock.expectLastCall().andThrow(new RuntimeException("This is an intentional Exception")).anyTimes(); 
+0

@LuiggiMendoza 죄송합니다. – NiematojakTomasz

+0

이것은'java.lang.IllegalStateException : 모의 객체에 대한 마지막 호출이 없습니다 .'를 던집니다. 나는'CacheWrapper'가 아니라'SomeClient # getEntity'를 테스트하고 있습니다. –

+0

@LuiggiMendoza 오케이, 오해했습니다. 그래서, 당신은. getEntity() 예외를 던져 잡을 것을 의미합니까? – fge

2

당신이 EasyMock에 또는 Mockito를 사용하고 있습니까? 둘 다 다른 프레임 워크입니다.

PowerMockito는이 두 프레임 워크에서 모두 사용할 수있는 수퍼 세트 (또는 그 이상)입니다. PowerMockito를 사용하면 Mockito 또는 EasyMock에서 수행하지 않는 작업을 수행 할 수 있습니다.

EasyMock에 :

예외를 던져 무효 방법을 스텁이 시도

// First make the actual call to the void method. 
cacheWrapper.putInSharedMemory("key", "value"); 
EasyMock.expectLastCall().andThrow(new RuntimeException()); 

확인 :

Mockito :

// Create a CacheWrapper spy and stub its method to throw an exception. 
// Syntax for stubbing a spy's method is different from stubbing a mock's method (check Mockito's docs). 
CacheWrapper spyCw = spy(new CacheWrapper()); 
Mockito.doThrow(new RuntimeException()) 
    .when(spyCw) 
    .putInSharedMemory(Matchers.any(), Matchers.any()); 

SomeClient sc = new SomeClient(); 
sc.setCacheWrapper(spyCw); 

// This will call spyCw#putInSharedMemory that will throw an exception. 
sc.getEntity("key"); 
관련 문제