Mockito를 사용하여 클래스의 모의 멤버 변수
나는 특히 개발과 단위 테스트에 초보자입니다. 내 요구 사항이 매우 간단하다고 생각하지만 다른 사람들의 생각을 알고 싶어합니다.
내가 두 개의 클래스를 가지고 있다고 가정 해보십시오.
public class First {
Second second ;
public First(){
second = new Second();
}
public String doSecond(){
return second.doSecond();
}
}
class Second {
public String doSecond(){
return "Do Something";
}
}
First.doSecond()
방법 을 테스트 하기 위해 단위 테스트를 작성한다고 가정 해 봅시다 . 그러나 Second.doSecond()
클래스 를 Mock하고 싶습니다. Mockito를 사용 하여이 작업을 수행하고 있습니다.
public void testFirst(){
Second sec = mock(Second.class);
when(sec.doSecond()).thenReturn("Stubbed Second");
First first = new First();
assertEquals("Stubbed Second", first.doSecond());
}
조롱이 적용되지 않고 어설 션이 실패한다는 것을 알았습니다. 테스트하려는 클래스의 멤버 변수를 조롱 할 수있는 방법이 없습니까? ?
모의를 전달할 수 있도록 멤버 변수에 액세스하는 방법을 제공해야합니다 (가장 일반적인 방법은 setter 메소드 또는 매개 변수를 취하는 생성자입니다).
코드에서이를 수행하는 방법을 제공하지 않으면 TDD (Test Driven Development)에 대해 잘못 고려됩니다.
코드를 변경할 수 없으면 불가능합니다. 그러나 의존성 주입을 좋아하고 Mockito가 지원합니다.
public class First {
@Resource
Second second;
public First() {
second = new Second();
}
public String doSecond() {
return second.doSecond();
}
}
당신의 시험 :
@RunWith(MockitoJUnitRunner.class)
public class YourTest {
@Mock
Second second;
@InjectMocks
First first = new First();
public void testFirst(){
when(second.doSecond()).thenReturn("Stubbed Second");
assertEquals("Stubbed Second", first.doSecond());
}
}
이것은 매우 좋고 쉽습니다.
코드를 자세히 보면 second
테스트 의 속성이 여전히 Second
mock이 아닌 의 인스턴스 라는 것을 알 수 first
있습니다 (코드에서 mock을 전달하지 않음 ).
가장 간단한 방법은 클래스 second
에서 setter를 만들고 First
모의 객체를 명시 적으로 전달하는 것입니다.
이처럼 :
public class First {
Second second ;
public First(){
second = new Second();
}
public String doSecond(){
return second.doSecond();
}
public void setSecond(Second second) {
this.second = second;
}
}
class Second {
public String doSecond(){
return "Do Something";
}
}
....
public void testFirst(){
Second sec = mock(Second.class);
when(sec.doSecond()).thenReturn("Stubbed Second");
First first = new First();
first.setSecond(sec)
assertEquals("Stubbed Second", first.doSecond());
}
다른 하나는 Second
인스턴스를 First
생성자 매개 변수 로 전달하는 것 입니다.
코드를 수정할 수 없다면 리플렉션을 사용하는 것이 유일한 옵션이라고 생각합니다.
public void testFirst(){
Second sec = mock(Second.class);
when(sec.doSecond()).thenReturn("Stubbed Second");
First first = new First();
Field privateField = PrivateObject.class.
getDeclaredField("second");
privateField.setAccessible(true);
privateField.set(first, sec);
assertEquals("Stubbed Second", first.doSecond());
}
But you probably can, as it's rare to do tests on code you don't control (although one can imagine a scenario where you have to test an external library cause it's author didn't :))
If you can't change the member variable, then the other way around this is to use powerMockit and call
Second second = mock(Second.class)
when(second.doSecond()).thenReturn("Stubbed Second");
whenNew(Second.class).withAnyArguments.thenReturn(second);
Now the problem is that ANY call to new Second will return the same mocked instance. But in your simple case this will work.
I had the same issue where a private value was not set because Mockito does not call super constructors. Here is how I augment mocking with reflection.
First, I created a TestUtils class that contains many helpful utils including these reflection methods. Reflection access is a bit wonky to implement each time. I created these methods to test code on projects that, for one reason or another, had no mocking package and I was not invited to include it.
public class TestUtils {
// get a static class value
public static Object reflectValue(Class<?> classToReflect, String fieldNameValueToFetch) {
try {
Field reflectField = reflectField(classToReflect, fieldNameValueToFetch);
reflectField.setAccessible(true);
Object reflectValue = reflectField.get(classToReflect);
return reflectValue;
} catch (Exception e) {
fail("Failed to reflect "+fieldNameValueToFetch);
}
return null;
}
// get an instance value
public static Object reflectValue(Object objToReflect, String fieldNameValueToFetch) {
try {
Field reflectField = reflectField(objToReflect.getClass(), fieldNameValueToFetch);
Object reflectValue = reflectField.get(objToReflect);
return reflectValue;
} catch (Exception e) {
fail("Failed to reflect "+fieldNameValueToFetch);
}
return null;
}
// find a field in the class tree
public static Field reflectField(Class<?> classToReflect, String fieldNameValueToFetch) {
try {
Field reflectField = null;
Class<?> classForReflect = classToReflect;
do {
try {
reflectField = classForReflect.getDeclaredField(fieldNameValueToFetch);
} catch (NoSuchFieldException e) {
classForReflect = classForReflect.getSuperclass();
}
} while (reflectField==null || classForReflect==null);
reflectField.setAccessible(true);
return reflectField;
} catch (Exception e) {
fail("Failed to reflect "+fieldNameValueToFetch +" from "+ classToReflect);
}
return null;
}
// set a value with no setter
public static void refectSetValue(Object objToReflect, String fieldNameToSet, Object valueToSet) {
try {
Field reflectField = reflectField(objToReflect.getClass(), fieldNameToSet);
reflectField.set(objToReflect, valueToSet);
} catch (Exception e) {
fail("Failed to reflectively set "+ fieldNameToSet +"="+ valueToSet);
}
}
}
Then I can test the class with a private variable like this. This is useful for mocking deep in class trees that you have no control as well.
@Test
public void testWithRectiveMock() throws Exception {
// mock the base class using Mockito
ClassToMock mock = Mockito.mock(ClassToMock.class);
TestUtils.refectSetValue(mock, "privateVariable", "newValue");
// and this does not prevent normal mocking
Mockito.when(mock.somthingElse()).thenReturn("anotherThing");
// ... then do your asserts
}
I modified my code from my actual project here, in page. There could be a compile issue or two. I think you get the general idea. Feel free to grab the code and use it if you find it useful.
Lots of others have already advised you to rethink your code to make it more testable - good advice and usually simpler than what I'm about to suggest.
If you can't change the code to make it more testable, PowerMock: https://code.google.com/p/powermock/
PowerMock extends Mockito (so you don't have to learn a new mock framework), providing additional functionality. This includes the ability to have a constructor return a mock. Powerful, but a little complicated - so use it judiciously.
You use a different Mock runner. And you need to prepare the class that is going to invoke the constructor. (Note that this is a common gotcha - prepare the class that calls the constructor, not the constructed class)
@RunWith(PowerMockRunner.class)
@PrepareForTest({First.class})
Then in your test set-up, you can use the whenNew method to have the constructor return a mock
whenNew(Second.class).withAnyArguments().thenReturn(mock(Second.class));
Yes, this can be done, as the following test shows (written with the JMockit mocking API, which I develop):
@Test
public void testFirst(@Mocked final Second sec) {
new NonStrictExpectations() {{ sec.doSecond(); result = "Stubbed Second"; }};
First first = new First();
assertEquals("Stubbed Second", first.doSecond());
}
With Mockito, however, such a test cannot be written. This is due to the way mocking is implemented in Mockito, where a subclass of the class to be mocked is created; only instances of this "mock" subclass can have mocked behavior, so you need to have the tested code use them instead of any other instance.
If you want an alternative to ReflectionTestUtils from Spring in mockito, use
Whitebox.setInternalState(first, "second", sec);
참고URL : https://stackoverflow.com/questions/8995540/mocking-member-variables-of-a-class-using-mockito
'Programming' 카테고리의 다른 글
스칼라 변수에 대한 SQL Server 출력 절 (0) | 2020.07.11 |
---|---|
다운로드 한 글꼴을 해독하지 못했습니다. OTS 구문 분석 오류 : 잘못된 버전 태그 + 레일 4 (0) | 2020.07.11 |
Android-카메라 미리보기가 옆으로 (0) | 2020.07.11 |
.csproj 파일에서 (0) | 2020.07.11 |
READ_COMMITTED_SNAPSHOT가 활성화되어 있는지 감지하는 방법은 무엇입니까? (0) | 2020.07.11 |