If you’re using JUnit, Mockito and PowerMock to write unit tests in Android Studio you probably often need to mock calls to android.util.Log. That’s because by default in Android Studio unit tests are executed against a modified version of the android.jar library which contains stubs only. If anywhere in your tested code you have a log statement that you don’t mock you will get a RuntimeException.
For example a call like
Log.d("tag", "message");
will throw a RuntimeException:
RuntimeException: Method d in android.util.Log not mocked. See http://g.co/androidstudio/not-mocked for details.
Wouldn’t it be nice if just all the log message would be redirected automatically to the unit test output?
Fortunately PowerMockito provides the possibility to create MockPolicies. Using a mock policy we can setup the log redirection once and then just use an annotation (on the class containing the test cases) to apply it.
Usage example:
@RunWith(PowerMockRunner.class) @MockPolicy(LogRedirection.class) public class MyTests { @Test public void test1() { } }
To create a log redirection mock policy we need to implement the PowerMockPolicy interface. The two methods on this interface allow us to apply a class loading policy and an interception policy.
public interface PowerMockPolicy { void applyClassLoadingPolicy(MockPolicyClassLoadingSettings settings); void applyInterceptionPolicy(MockPolicyInterceptionSettings settings); }
In the first method we need to tell PowerMock which classes should be modified by the mock class loader before these classes are loaded. In our case it’s just the android.util.Log class. In the second method we create the mocks for the methods of the Log class which should just redirect the messages to the standard output.
The following code shows a sample implementation for a mock policy that redirects all log calls to the standard output.
import android.util.Log; import org.powermock.core.spi.PowerMockPolicy; import org.powermock.mockpolicies.MockPolicyClassLoadingSettings; import org.powermock.mockpolicies.MockPolicyInterceptionSettings; import java.io.PrintWriter; import java.io.StringWriter; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; public class LogRedirection implements PowerMockPolicy { @Override public void applyClassLoadingPolicy(MockPolicyClassLoadingSettings settings) { settings.addFullyQualifiedNamesOfClassesToLoadByMockClassloader(Log.class.getName()); } @Override public void applyInterceptionPolicy(MockPolicyInterceptionSettings settings) { try { // Mock Log.v(String tag, String msg) settings.proxyMethod(Log.class.getMethod("v", String.class, String.class), new InvocationHandler() { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { String logLevel = method.getName().toUpperCase(); String tag = args[0].toString(); String message = args[1].toString(); return redirect(logLevel, tag, message, null); } }); // Mock Log.v(String tag, String msg, Throwable tr) settings.proxyMethod(Log.class.getMethod("v", String.class, String.class, Throwable.class), new InvocationHandler() { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { String logLevel = method.getName().toUpperCase(); String tag = args[0].toString(); String message = args[1].toString(); Throwable throwable = (Throwable) args[2]; return redirect(logLevel, tag, message, throwable); } }); // Mock Log.d(String tag, String msg) settings.proxyMethod(Log.class.getMethod("d", String.class, String.class), new InvocationHandler() { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { String logLevel = method.getName().toUpperCase(); String tag = args[0].toString(); String message = args[1].toString(); return redirect(logLevel, tag, message, null); } }); // Mock Log.d(String tag, String msg, Throwable tr) settings.proxyMethod(Log.class.getMethod("d", String.class, String.class, Throwable.class), new InvocationHandler() { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { String logLevel = method.getName().toUpperCase(); String tag = args[0].toString(); String message = args[1].toString(); Throwable throwable = (Throwable) args[2]; return redirect(logLevel, tag, message, throwable); } }); // Mock Log.i(String tag, String msg) settings.proxyMethod(Log.class.getMethod("i", String.class, String.class), new InvocationHandler() { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { String logLevel = method.getName().toUpperCase(); String tag = args[0].toString(); String message = args[1].toString(); return redirect(logLevel, tag, message, null); } }); // Mock Log.i(String tag, String msg, Throwable tr) settings.proxyMethod(Log.class.getMethod("i", String.class, String.class, Throwable.class), new InvocationHandler() { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { String logLevel = method.getName().toUpperCase(); String tag = args[0].toString(); String message = args[1].toString(); Throwable throwable = (Throwable) args[2]; return redirect(logLevel, tag, message, throwable); } }); // Mock Log.w(String tag, String msg) settings.proxyMethod(Log.class.getMethod("w", String.class, String.class), new InvocationHandler() { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { String logLevel = method.getName().toUpperCase(); String tag = args[0].toString(); String message = args[1].toString(); return redirect(logLevel, tag, message, null); } }); // Mock Log.w(String tag, String msg, Throwable tr) settings.proxyMethod(Log.class.getMethod("w", String.class, String.class, Throwable.class), new InvocationHandler() { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { String logLevel = method.getName().toUpperCase(); String tag = args[0].toString(); String message = args[1].toString(); Throwable throwable = (Throwable) args[2]; return redirect(logLevel, tag, message, throwable); } }); // Mock Log.w(String tag, Throwable tr) settings.proxyMethod(Log.class.getMethod("w", String.class, Throwable.class), new InvocationHandler() { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { String logLevel = method.getName().toUpperCase(); String tag = args[0].toString(); String message = getStackTraceString((Throwable) args[1]); return redirect(logLevel, tag, message, null); } }); // Mock Log.e(String tag, String msg) settings.proxyMethod(Log.class.getMethod("e", String.class, String.class), new InvocationHandler() { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { String logLevel = method.getName().toUpperCase(); String tag = args[0].toString(); String message = args[1].toString(); return redirect(logLevel, tag, message, null); } }); // Mock Log.e(String tag, String msg, Throwable tr) settings.proxyMethod(Log.class.getMethod("e", String.class, String.class, Throwable.class), new InvocationHandler() { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { String logLevel = method.getName().toUpperCase(); String tag = args[0].toString(); String message = args[1].toString(); Throwable throwable = (Throwable) args[2]; return redirect(logLevel, tag, message, throwable); } }); } catch (NoSuchMethodException e) { e.printStackTrace(); } } private static int redirect(String logLevel, String tag, String message, Throwable throwable) { if (throwable == null) { System.out.println(String.format("%s - %s: %s", logLevel, tag, message)); } else { System.out.println(String.format("%s - %s: %s", logLevel, tag, message + '\n' + getStackTraceString(throwable))); } return 0; } private static String getStackTraceString(Throwable tr) { StringWriter sw = new StringWriter(); PrintWriter pw = new PrintWriter(sw); tr.printStackTrace(pw); pw.flush(); return sw.toString(); } }