如何获取用于单元测试的 InvocationContext 实例

How to get intance of InvocationContext for unit tests

我正在尝试为将 InvocationContext 作为参数的方法编写单元测试。更具体地说,这是该方法的签名和要点。

@AroundInvoke
public Object autoLogMethodCall(final InvocationContext context) throws Exception {

    String className = context.getClass().getSimpleName();
    String packageName = context.getClass().getPackage().getName();
    String methodName = context.getMethod().getName();

    // Some logging stuff that is the target of actual testing
}

如您所见,这是我打算用于对某些方法调用进行一些基本日志记录的拦截器方法。

然后我进行单元测试,我想测试记录的消息是否格式正确。但问题是我无法创建 InvocationContext 的实例作为测试参数传递。

我试过以下模拟。

@RunWith(PowerMockRunner.class)
public class AutoLoggingTest extends TestCase {

    @Test
    public void testAutoLogger() {
        Logger log = new MyLogger(); // This is an implementation of org.apache.logging.log4j.Logger, which will hold the generated messages to check at the test
        InvocationContext mockContext = PowerMockito.mock(InvocationContext.class);
        Class clazz = AutoLoggingTest.class;
        // The row causing the error 'MissingMethodInvocation'
        PowerMockito.when(mockContext.getClass()).thenReturn(clazz);

try {
    InterceptingClass ic = new InterceptingClass();
    ic.setLogger(log);
    ic.autoLogMethodCall(mockContext);
    MyLogger myLogger = (MyLogger) ic.getLogger();
    assertEquals(2, myLogger.getMessages().size());
        } catch (Exception e) {
            e.printStackTrace();
            fail("Should not cause an exception in any case");
        }
    }
    // Check the actual messages based on the information given in mocked InvocationContext object
}

但是不行。
原因:

Tests in error: AutoLoggingTest.testAutoLogger:25 » MissingMethodInvocation.
when() requires an argument which has to be 'a method call on a mock'.).

关于如何正确进行模拟的任何建议?

这需要一些开箱即用的思考。需要一些与模拟的 InvocationContext 混合的内容。我们可以在模拟的 InvocationContext 对象中提供测试 class 本身,因此我在测试 class 本身中添加并更改了以下内容:

@RunWith(PowerMockRunner.class)
public class AutoLoggingTest extends TestCase {

    // This method needs to be added here to provide it for mocked InvocationContext.
    public void methodForLoggingTesting() {

    }

    @Test
    public void testAutoLogger() {

        Logger log = new MyLogger();
        // Some renaming & refactoring after the initial stuff
        AutoLoggingUtility alu = new AutoLoggingUtilityImplForTesting();
        alu.setLogger(log);
        InvocationContext mockContext = PowerMockito.mock(InvocationContext.class);
        try {
            Method testMethod = this.getClass().getMethod("methodForLoggingTesting");
            PowerMockito.when(mockContext.getMethod()).thenReturn(testMethod);
            PowerMockito.when(mockContext.proceed()).thenReturn(null);
        } catch (Exception e) {
            e.printStackTrace();
            fail("Should not throw an exception, InvocationContext mocking failed!");
        }
        try {
            alu.autoLogMethodCall(mockContext);
        } catch (Exception e) {
            e.printStackTrace();
            fail("Should not throw an exception, logging failed!");
        }
        MyLogger myLogger = (MyLogger) alu.getLogger();
        assertEquals(3, myLogger.getMessages().size());

        // More tests to check the actual logged content
    }
}

我还意识到我应该提供 'MyLogger' 的代码,因为它在测试中的实现并不简单。

// Logger = org.apache.logging.log4j.Logger
// ExtendedLoggerWrapper = org.apache.logging.log4j.spi.ExtendedLoggerWrapper
@SuppressWarnings("serial")
protected class MyLogger extends ExtendedLoggerWrapper implements Logger {
    private List<String> messages;

    public MyLogger() {
        super(null, null, null);
        this.clearMessages();
    }

    // The actual log calls need to get stored to store the messages + prevent from NullPointerExceptions
    @Override
    public void trace(String msg) {
        messages.add(msg);
    }

    // The actual log calls need to get stored to store the messages + prevent from NullPointerExceptions
    @Override
    public Object exit(Object obj) {
        messages.add("Exited with: " + obj);
        return obj;
    }

    public List<String> getMessages() {
        return this.messages;
    }

    public void clearMessages() {
        messages = new ArrayList<>();
    }

    /**
     * You need to override all the method calls used to prevent NullPointerExceptions.
     *
     * @return <code>True</code> always, as required so in test.
     */
    @Override
    public boolean isTraceEnabled() {
        return true;
    }
}

并且由于在原始 Logging class 中需要进行一些小的重构,它现在看起来像这样:

public abstract class AutoLoggingUtility {

    private static final String logEntryTemplate = "Call to: %1$s#%2$s";
    private static final String logExitTemplate = "'%1$s' call duration: %2$s ms";

    public AutoLoggingUtility() {

    }

    @AroundInvoke
    public Object autoLogMethodCall(final InvocationContext context) throws Exception {
    // Note the methods Overridden in MyLogger
    if (this.getLogger().isTraceEnabled()) {
        String methodName = null;
        String className = null;
        try {
            Method method = context.getMethod();
            methodName = method.getName();
            // Contains package
            className = context.getMethod().getDeclaringClass().getName();
            } catch (Exception e) {
                // May not crash
                methodName = "?method?";
                className = "?class?";
            }
            Object[] args1 = { className, methodName };
            String logMsg = String.format(getLogentrytemplate(), args1);
            this.getLogger().trace(logMsg);

            long startTime = System.currentTimeMillis();
            try {
            return this.getLogger().exit(context.proceed());
            } finally {
            Object[] args2 = { methodName, System.currentTimeMillis() - startTime };
            logMsg = String.format(getLogexittemplate(), args2);
            this.getLogger().trace(logMsg);
        }
    } else {
        // mocked
        return context.proceed();
    }

    /**
     * Forces each extending class to provide their own logger.
     *
     * @return The logger of the extending class to direct the messages to correct logging context.
     */
    abstract Logger getLogger();
}

AutoLoggingUtilityImplForTesting”只是扩展了“AutoLoggingUtility”以保存 MyLogger 的实例。

摘要:
诀窍是当“getMethod() ' 被调用。 => 无需尝试模拟多余的东西。