7

I would like to log information in model classes - not necessarily for unit testing purposes but for real life scenarios where I am trying to debug.

However, if I try to use android.util.Log methods I get the following errors when running JUnit tests:

java.lang.RuntimeException: Method d in android.util.Log not mocked. See http://g.co/androidstudio/not-mocked for details.

I understand why this occurs, I should not be using Android framework code in model classes that are designed to be framework independent! I'm not really arguing against the error, but rather I am trying to find a way to work around this.

I have one idea, does this make sense?

Create a CustomLog class along these lines:

public class CustomLog {
    private static ILogger mLogger;

    public static void setLogger(ILogger logger) {
        mLogger = logger;
    }

    public static void e(String tag, String message) {
        mLogger.e(tag, message);
    }
}

Where ILogger is an interface with the required methods to perform the log functionality (e, d, etc. methods...)

I could create an ILoggerImpl that uses the android.util.Log methods, and a MockLogger class that simply prints out to System.out.println and/or does nothing (or anything else!).

I think that'd perfectly fit my needs (I would be required to setup my CustomLog class very early on in the lifecycle, but that's not a huge deal).

However, if I ever needed to add third party libraries/outside code to my model classes, this would likely break again in the same manner if the new libraries/code use android.util.Log methods.

So, is there a "catch all" type behavior I could use? What do you think?

Zach
  • 3,909
  • 6
  • 25
  • 50
  • 1
    You can use `System.out.println()` which is maybe not perfect, but shows up in the test outputs. – zsmb13 Mar 06 '17 at 20:03
  • Definitely. I explained that here: "I could create an `ILoggerImpl` that uses the `android.util.Log` methods, and a `MockLogger` class that simply prints out to `System.out.println` and/or does nothing (or anything else!)." - my question pertains to _how_ to get this working nicely. `System.out.println` for unit testing but `android.util.Log` for debug/release non unit testing! – Zach Mar 06 '17 at 20:05
  • 1
    Oh right, sorry. I think you found the best you can really do then. – zsmb13 Mar 06 '17 at 20:07
  • _I_ think so too ;) - but I'd like to get more thoughts if possible! Thank you for your feedback though! – Zach Mar 06 '17 at 20:10

1 Answers1

0

One way of solving the "Not mocked" exception you cited is to use PowerMockito to mock the Logging methods. Instead of calling PowerMockito.mockStatic(Log.class); as explained in the linked answer, you can take inspiration from this, and use PowerMockito to replace() Android.util's Log.v, Log.d, Log.i & Log.e with methods that will run System.out.println instead. This allows you to see the logged messages in Android Studio's Run window.

String[] logMethods = {"v", "d", "i", "e"};
InvocationHandler systemOutPrintln = new InvocationHandler() {
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        StringBuilder messageBuilder = new StringBuilder();
        for (int i = 0; i < args.length; i++) {
            String arg = args[i].toString();
            messageBuilder.append(arg);
            // add separators, ASCII art, ...
        }
        System.out.println(messageBuilder);
        return messageBuilder.toString().length();
    }
};

for (String logMethod : logMethods) {
    replace(method(Log.class, logMethod, String.class, String.class)).with(systemOutPrintln);
    replace(method(Log.class, logMethod, String.class, String.class, Throwable.class)).with(systemOutPrintln);
}

Disclaimer: I'm not sure if the above is the most idiomatic implementation.

m01
  • 9,033
  • 6
  • 32
  • 58
  • Personally I think this violates the principle that model code should not have Android dependencies in it, at all, but sure this _would_ work I think! – Zach Aug 18 '17 at 02:30