我正在尝试为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()需要一个必须是'模拟方法调用'的参数。)。
有关如何正确进行模拟的任何建议吗?
答案 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;无需尝试嘲笑多余的东西。