June test log messages for Android

Is there a way to print Logcat messages (Log.i, Log.d) when running the JUnit test (method) in Android Studio?

I can see the System.out.print message, but not print logcat.

The runconfiguration (Android Studio GUI window) has logcat options for tests in Android tests, but not for JUnit tests.

Is it possible somehow? Thanks for any tips!

+9
android android-studio junit logcat
source share
3 answers

Logcat output will not be displayed in unit tests since Logcat is an Android feature. JUnit tests can only use standard Java, so Android features will not work.

What you can do in unit tests is to introduce β€œtest doubles” in the components under test. But Log.x calls are static, so you cannot redefine them (without permission, for example, PowerMock, which you should avoid at all costs).

Therefore, the first step is to introduce a non-static class that will act as a proxy for Log.x calls:

 /** * This class is a non-static logger */ public class Logger { public void e(String tag, String message) { Log.e(tag, message); } public void w(String tag, String message) { Log.w(tag, message); } public void v(String tag, String message) { Log.v(tag, message); } public void d(String tag, String message) { Log.d(tag, message); } } 

Use this class in all places where there are Log.x calls.

The second step is to write a test dual implementation of Logger that redirects to standard output:

 public class UnitTestLogger extends Logger{ @Override public void e(String tag, String message) { System.out.println("E " + tag + ": " + message); } // similar for other methods } 

The final step is to introduce UnitTestLogger instead of Logger in unit tests:

 @RunWith(MockitoJUnitRunner.class) public class SomeClassTest { private Logger mLogger = new UnitTestLogger(); private SomeClass SUT; @Before public void setup() throws Exception { SUT = new SomeClass(/* other dependencies here */ mLogger); } } 

If you want to adhere strictly to the principles of OOP, you can extract a common interface for Logger and UnitTestLogger .

However, I never encountered the need to investigate Log.x calls in unit tests. I suspect you do not need this either. You can run unit tests in debug mode and step through the code line by line in the debugger, which is much faster than trying to examine the output of logcat ...

General advice:

If your test code contains static calls to Log.x , and your unit tests don't fail, you have a problem.

I would suggest that either all tests are performed using Robolectric , or you have this statement in build.gradle: unitTests.returnDefaultValues = true .

If you run all tests using Robolectric , then this is simply inefficient, but if all Android calls return to default values, then the test suite is not reliable. I suggest you fix this problem before continuing, because it will somehow bite you in the future.

+8
source share

I searched the same thing and did not find a direct answer. I know this question is more than a year old, but still, it would be nice to get an answer here for future reference.

The android.util.Log journal is directly linked to Logcat, and the implementation for android.util.Log is not available when performing unit tests on the local JVM. You will receive an error message when you try to use the log class in your unit tests, because "the android.jar file used to run unit tests does not contain any real code (these APIs are provided only on the Android system image on the device)."
See Android documentation for device testing

So, if you really want to use android.util.Log, you will need to mock it locally and use System.out.print to print to the console. To get started, add PowerMockito to your project. If you use Gradle, you can simply add the following dependencies:

 testCompile 'junit:junit:4.12' testCompile 'org.powermock:powermock:1.6.5' testCompile 'org.powermock:powermock-module-junit4:1.6.5' testCompile 'org.powermock:powermock-api-mockito:1.6.5' 

Next, I used the Steve answer here to figure out how to return the parameter passed to the mock using Mockito.

The result is something like:

 import android.util.Log; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.invocation.InvocationOnMock; import org.mockito.stubbing.Answer; import org.powermock.api.mockito.PowerMockito; import org.powermock.core.classloader.annotations.PrepareForTest; import org.powermock.modules.junit4.PowerMockRunner; import static org.mockito.Matchers.anyString; import static org.powermock.api.mockito.PowerMockito.when; @RunWith(PowerMockRunner.class) @PrepareForTest({Log.class}) public class SomeUnitTest { @Test public void testSomething() { System.out.println("Running test"); PowerMockito.mockStatic(Log.class); // Log warnings to the console when(Log.w(anyString(), anyString())).thenAnswer(new Answer<Void>() { @Override public Void answer(InvocationOnMock invocation) throws Throwable { Object[] args = invocation.getArguments(); if (args.length > 1) { //cause I'm paranoid System.out.println("Tag:" + args[0] + " Msg: " + args[1]); } return null; } }); Log.w("My Tag", "This is a warning"); } } 

Hope this helps someone!

+2
source share

I would go with the @Vasiliy approach, but with a little change. Instead of using System.out.println for logging, we can actually use the Java Logger configured with the ConsoleHandler to log messages on the test output screen.

This can be achieved in the following simple steps.

Step 1: Define Your Own Log Level

  public enum LogLevel { VERBOSE, DEBUG, INFO, WARNING, ERROR, SILENT } 

Step 2: Define the Logger Abstraction

 public abstract class Logger { .... public abstract void debug(String tag, String message); public abstract void error(String tag, String message); .... } 

Step 3: implementation based on java.util.Logging for Logger

 public class JavaLogger extends Logger { private java.util.logging.Logger mLogger; public JavaLogger(LogLevel logLevel, String name) { mLogger = java.util.logging.Logger.getLogger(name); ConsoleHandler handler = new ConsoleHandler(); Level level = mapLogLevel(logLevel); handler.setLevel(level); mLogger.addHandler(handler); mLogger.setLevel(level); } @Override public void debug(String tag, String message) { if (isLoggable(LogLevel.DEBUG)) { mLogger.finer(message); } } @Override public void error(String tag, String message) { if (isLoggable(LogLevel.ERROR)) { mLogger.severe(message); } } .... private Level mapLogLevel(LogLevel logLevel) { Level level = Level.OFF; switch (logLevel) { case INFO: level = Level.INFO; break; case WARNING: level = Level.WARNING; break; case ERROR: level = Level.SEVERE; break; case VERBOSE: level = Level.FINEST; break; case DEBUG: level = Level.FINER; break; case SILENT: level = Level.OFF; break; default: // no impl } return level; } } 

Step 4: implementation based on android.util.Log for Logger

 public class AndroidLogger extends Logger { public AndroidLogger(LogLevel logLevel) { super(logLevel); } .... @Override public void debug(String tag, String message) { if (isLoggable(LogLevel.DEBUG)) { Log.d(tag, message, null); } } @Override public void error(String tag, String message) { if (isLoggable(LogLevel.ERROR)) { Log.e(tag, message, tr); } } .... 

}

Step 5: Create a Simple Wrapper Class for Implementation Through Logger

  public class AppLogger { private static Logger sLogger; public static void init(Logger logger) { sLogger = logger; } public static void debug(final String tag, String message) { sLogger.debug(tag, message); } public static void error(final String tag, String message) { sLogger.error(tag, message, null); } public static void error(final String tag, String message, Throwable t) { sLogger.error(tag, message, t); } ... } 

Step 6: Initialization

Android

Using the onCreate Method

 AppLogger.init(new AndroidLogger(LogLevel.DEBUG)); 

Junit

AppLogger can be initialized either in @BeforeClass or in @Before

 AppLogger.init(new JavaLogger(LogLevel.DEBUG, "test-logger")); 

Now you can watch the log message in the test window of Android Studio.

0
source share

All Articles