IT story

Mockito를 사용하여 추상 클래스 테스트

hot-time 2020. 5. 8. 08:24
반응형

Mockito를 사용하여 추상 클래스 테스트


추상 클래스를 테스트하고 싶습니다. 물론, 클래스에서 상속받은 모형수동으로 작성할있습니다 .

내 모의를 만드는 대신 모의 프레임 워크 (Mockito를 사용하고 있음)를 사용 하여이 작업을 수행 할 수 있습니까? 어떻게?


다음 제안에서는 "실제"서브 클래스를 작성하지 않고 추상 클래스를 테스트 할 수 있습니다 . Mock 서브 클래스입니다.

를 사용한 Mockito.mock(My.class, Mockito.CALLS_REAL_METHODS)다음 호출되는 추상 메소드를 조롱하십시오.

예:

public abstract class My {
  public Result methodUnderTest() { ... }
  protected abstract void methodIDontCareAbout();
}

public class MyTest {
    @Test
    public void shouldFailOnNullIdentifiers() {
        My my = Mockito.mock(My.class, Mockito.CALLS_REAL_METHODS);
        Assert.assertSomething(my.methodUnderTest());
    }
}

참고 :이 솔루션의 장점은 결코 호출되지 않는 한 추상 메소드를 구현할 필요가 없다는 입니다.

정직한 견해로는 스파이가 인스턴스를 필요로하기 때문에 스파이를 사용하는 것보다 깔끔합니다. 즉, 추상 클래스의 인스턴스화 가능한 서브 클래스를 만들어야합니다.


초록을 건드리지 않고 구체적인 방법 중 일부를 테스트 해야하는 경우 CALLS_REAL_METHODS( 모르 텐의 답변 참조)을 사용할 수 있지만 테스트중인 구체적인 방법이 초록 또는 구현되지 않은 인터페이스 방법 중 일부를 호출하면 작동하지 않습니다. -Mockito는 "자바 인터페이스에서 실제 메소드를 호출 할 수 없습니다"라고 불평합니다.

(예, 디자인이 거칠지 만 Tapestry 4와 같은 일부 프레임 워크는 사용자에게 영향을줍니다.)

해결 방법은이 접근 방식을 뒤집는 것입니다. 일반적인 모의 동작 (즉, 모든 모의 / 스터브)을 사용 doCallRealMethod()하고 테스트중인 구체적인 방법을 명시 적으로 호출하는 데 사용 합니다. 예 :

public abstract class MyClass {
    @SomeDependencyInjectionOrSomething
    public abstract MyDependency getDependency();

    public void myMethod() {
        MyDependency dep = getDependency();
        dep.doSomething();
    }
}

public class MyClassTest {
    @Test
    public void myMethodDoesSomethingWithDependency() {
        MyDependency theDependency = mock(MyDependency.class);

        MyClass myInstance = mock(MyClass.class);

        // can't do this with CALLS_REAL_METHODS
        when(myInstance.getDependency()).thenReturn(theDependency);

        doCallRealMethod().when(myInstance).myMethod();
        myInstance.myMethod();

        verify(theDependency, times(1)).doSomething();
    }
}

다음을 추가하도록 업데이트되었습니다.

무효가 아닌 방법의 경우 thenCallRealMethod()대신 다음 을 사용해야 합니다.

when(myInstance.myNonVoidMethod(someArgument)).thenCallRealMethod();

그렇지 않으면 Mockito는 "완료되지 않은 스터 빙이 감지되었습니다"라고 불평합니다.


You can achieve this by using a spy (use the latest version of Mockito 1.8+ though).

public abstract class MyAbstract {
  public String concrete() {
    return abstractMethod();
  }
  public abstract String abstractMethod();
}

public class MyAbstractImpl extends MyAbstract {
  public String abstractMethod() {
    return null;
  }
}

// your test code below

MyAbstractImpl abstractImpl = spy(new MyAbstractImpl());
doReturn("Blah").when(abstractImpl).abstractMethod();
assertTrue("Blah".equals(abstractImpl.concrete()));

Mocking frameworks are designed to make it easier to mock out dependencies of the class you are testing. When you use a mocking framework to mock a class, most frameworks dynamically create a subclass, and replace the method implementation with code for detecting when a method is called and returning a fake value.

When testing an abstract class, you want to execute the non-abstract methods of the Subject Under Test (SUT), so a mocking framework isn't what you want.

Part of the confusion is that the answer to the question you linked to said to hand-craft a mock that extends from your abstract class. I wouldn't call such a class a mock. A mock is a class that is used as a replacement for a dependency, is programmed with expectations, and can be queried to see if those expectations are met.

Instead, I suggest defining a non-abstract subclass of your abstract class in your test. If that results in too much code, than that may be a sign that your class is difficult to extend.

An alternative solution would be to make your test case itself abstract, with an abstract method for creating the SUT (in other words, the test case would use the Template Method design pattern).


Try using a custom answer.

For example:

import org.mockito.Mockito;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;

public class CustomAnswer implements Answer<Object> {

    public Object answer(InvocationOnMock invocation) throws Throwable {

        Answer<Object> answer = null;

        if (isAbstract(invocation.getMethod().getModifiers())) {

            answer = Mockito.RETURNS_DEFAULTS;

        } else {

            answer = Mockito.CALLS_REAL_METHODS;
        }

        return answer.answer(invocation);
    }
}

It will return the mock for abstract methods and will call the real method for concrete methods.


What really makes me feel bad about mocking abstract classes is the fact, that neither the default constructor YourAbstractClass() gets called (missing super() in mock) nor seems there to be any way in Mockito to default initialize mock properties (e.g List properties with empty ArrayList or LinkedList).

My abstract class (basically the class source code gets generated) does NOT provide a dependency setter injection for list elements, nor a constructor where it initializes the list elements (which I tried to add manually).

Only the class attributes use default initialization: private List dep1 = new ArrayList; private List dep2 = new ArrayList

So there is NO way to mock an abstract class without using a real object implementation (e.g inner class definition in unit test class, overriding abstract methods) and spying the real object (which does proper field initialization).

Too bad that only PowerMock would help here further.


Assuming your test classes are in the same package (under a different source root) as your classes under test you can simply create the mock:

YourClass yourObject = mock(YourClass.class);

and call the methods you want to test just as you would any other method.

You need to provide expectations for each method that is called with the expectation on any concrete methods calling the super method - not sure how you'd do that with Mockito, but I believe it's possible with EasyMock.

All this is doing is creating a concrete instance of YouClass and saving you the effort of providing empty implementations of each abstract method.

As an aside, I often find it useful to implement the abstract class in my test, where it serves as an example implementation that I test via its public interface, although this does depend on the functionality provided by the abstract class.


You can extend the abstract class with an anonymous class in your test. For example (using Junit 4):

private AbstractClassName classToTest;

@Before
public void preTestSetup()
{
    classToTest = new AbstractClassName() { };
}

// Test the AbstractClassName methods.

Mockito allows mocking abstract classes by means of the @Mock annotation:

public abstract class My {

    public abstract boolean myAbstractMethod();

    public void myNonAbstractMethod() {
        // ...
    }
}

@RunWith(MockitoJUnitRunner.class)
public class MyTest {

    @Mock(answer = Answers.CALLS_REAL_METHODS)
    private My my;

    @Test
    private void shouldPass() {
        BDDMockito.given(my.myAbstractMethod()).willReturn(true);
        my.myNonAbstractMethod();
        // ...
    }
}

The disadvantage is that it cannot be used if you need constructor parameters.


You can instantiate an anonymous class, inject your mocks and then test that class.

@RunWith(MockitoJUnitRunner.class)
public class ClassUnderTest_Test {

    private ClassUnderTest classUnderTest;

    @Mock
    MyDependencyService myDependencyService;

    @Before
    public void setUp() throws Exception {
        this.classUnderTest = getInstance();
    }

    private ClassUnderTest getInstance() {
        return new ClassUnderTest() {

            private ClassUnderTest init(
                    MyDependencyService myDependencyService
            ) {
                this.myDependencyService = myDependencyService;
                return this;
            }

            @Override
            protected void myMethodToTest() {
                return super.myMethodToTest();
            }
        }.init(myDependencyService);
    }
}

Keep in mind that the visibility must be protected for the property myDependencyService of the abstract class ClassUnderTest.


Whitebox.invokeMethod(..) can be handy in this case.

참고URL : https://stackoverflow.com/questions/1087339/using-mockito-to-test-abstract-classes

반응형