如何为单元测试获取InvocationContext的内容

时间:2016-09-29 04:52:22

标签: unit-testing java-ee-6 interceptor powermockito

我正在尝试为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
}

但它不起作用。
    原因:

  

错误测试:       AutoLoggingTest.testAutoLogger:25»MissingMethodInvocation。
      when()需要一个必须是'模拟方法调用'的参数。)。

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

1 个答案:

答案 0 :(得分:0)

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

@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类需要进行一些小的重构,现在它看起来像这样:

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 的实例。

Summarum:
诀窍是提供测试类方法' methodForLoggingTesting '的实例,以便在调用' getMethod()'时返回模拟对象。 =&GT;无需尝试嘲笑多余的东西。