Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Can't use logback for Android JUnit in Android Studio? #151

Closed
Gohan opened this issue Sep 23, 2017 · 14 comments
Closed

Can't use logback for Android JUnit in Android Studio? #151

Gohan opened this issue Sep 23, 2017 · 14 comments

Comments

@Gohan
Copy link

Gohan commented Sep 23, 2017

image
image

I had put "logback.xml" in
src\test\assets\logback.xml
src\main\assets\logback.xml
src\androidTest\assets\logback.xml

but seems it still not working

logback file below:

<configuration debug='true'>
    <property name="LOG_DIR" value="/sdcard/logback" />
    <appender name="LOG_CAT_APPENDER" class="ch.qos.logback.classic.android.LogcatAppender">
        <encoder class="demo2017.demos.baozs.com.common.logback.ExtendedPatternLayoutEncoder">
            <pattern>%d{HH:mm:ss.SSS} %PID %TID [%thread] %-5level %logger{36} - %msg%n</pattern>
        </encoder>
    </appender>
    <appender name="LOG_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <file>${LOG_DIR}/logfile.log</file>
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <fileNamePattern>${LOG_DIR}/logfile.%d{yyyy-MM-dd}.log.gz</fileNamePattern>
            <maxHistory>7</maxHistory>
            <totalSizeCap>1GB</totalSizeCap>
        </rollingPolicy>
        <encoder class="demo2017.demos.baozs.com.common.logback.ExtendedPatternLayoutEncoder">
            <pattern>%d{HH:mm:ss.SSS} %-4relative %PID %TID [%thread] %-5level %logger{35} - %msg %n</pattern>
        </encoder>
    </appender>
    <appender name="FLOW_LOG_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <file>${LOG_DIR}/flow_logfile.log</file>
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <fileNamePattern>${LOG_DIR}/flow_logfile.%d{yyyy-MM-dd}.log.gz</fileNamePattern>
            <maxHistory>7</maxHistory>
            <totalSizeCap>1GB</totalSizeCap>
        </rollingPolicy>
        <encoder class="demo2017.demos.baozs.com.common.logback.ExtendedPatternLayoutEncoder">
            <pattern>%d{HH:mm:ss.SSS} %-4relative %PID %TID [%thread] %-5level %logger{35} - %msg %n</pattern>
        </encoder>
    </appender>
    <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <pattern>%d{HH:mm:ss.SSS} %PID %TID [%thread] %-5level %logger{36} - %msg</pattern>
        </encoder>
    </appender>
    <root level="DEBUG">
        <appender-ref ref="LOG_CAT_APPENDER" />
        <appender-ref ref="LOG_FILE" />
    </root>
    <logger name="flow" level="DEBUG" additivity="false">
        <appender-ref ref="FLOW_LOG_FILE"/>
    </logger>
    <logger name="test" level="DEBUG" additivity="false">
        <appender-ref ref="STDOUT"/>
    </logger>
</configuration>

after some trying

add
testCompile 'ch.qos.logback:logback-classic:1.2.3'

it works but got Class path contains multiple SLF4J bindings. warning..

@stale
Copy link

stale bot commented Mar 27, 2018

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.

@stale stale bot added the wontfix label Mar 27, 2018
@stale stale bot closed this as completed Apr 3, 2018
@Gohan
Copy link
Author

Gohan commented Apr 4, 2018

Still unsolved

@cleemansen
Copy link

I can not see any output in my JUnit-Tests also. I got the following exception:

Failed to instantiate [ch.qos.logback.classic.LoggerContext]
Reported exception:
java.lang.RuntimeException: Method getExternalStorageState in android.os.Environment not mocked. See http://g.co/androidstudio/not-mocked for details.
	at android.os.Environment.getExternalStorageState(Environment.java)
	at ch.qos.logback.core.android.AndroidContextUtil.getMountedExternalStorageDirectoryPath(Unknown Source)
	at ch.qos.logback.core.android.AndroidContextUtil.setupProperties(Unknown Source)
	at ch.qos.logback.classic.util.ContextInitializer.autoConfig(Unknown Source)
	at org.slf4j.impl.StaticLoggerBinder.init(Unknown Source)
	at org.slf4j.impl.StaticLoggerBinder.<clinit>(Unknown Source)
	at org.slf4j.LoggerFactory.bind(LoggerFactory.java:150)
	at org.slf4j.LoggerFactory.performInitialization(LoggerFactory.java:124)
	at org.slf4j.LoggerFactory.getILoggerFactory(LoggerFactory.java:412)
	at org.slf4j.LoggerFactory.getLogger(LoggerFactory.java:357)
	at org.slf4j.LoggerFactory.getLogger(LoggerFactory.java:383)
	at com.couchbits.presence.data.sync.SyncEngine.<clinit>(SyncEngine.java:29)
	at com.couchbits.presence.data.sync.SyncEngineTest.setUp(SyncEngineTest.java:81)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.lang.reflect.Method.invoke(Method.java:498)
	at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
	at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
	at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
	at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:24)
	at org.junit.rules.ExpectedException$ExpectedExceptionStatement.evaluate(ExpectedException.java:239)
	at org.junit.rules.RunRules.evaluate(RunRules.java:20)
	at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
	at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:78)
	at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:57)
	at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
	at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
	at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
	at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
	at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
	at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:26)
	at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
	at org.mockito.internal.runners.JUnit45AndHigherRunnerImpl.run(JUnit45AndHigherRunnerImpl.java:37)
	at org.mockito.runners.MockitoJUnitRunner.run(MockitoJUnitRunner.java:62)
	at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
	at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:68)
	at com.intellij.rt.execution.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:47)
	at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:242)
	at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:70)

Adding testImplementation 'ch.qos.logback:logback-classic:1.2.3' will print the log into my Android-Studio-Console. But I get also the SLF4J: Class path contains multiple SLF4J bindings. warning.

Any advice how to setup a correct JUnit-Test-Setup?

@almozavr
Copy link

@cleemansen could you provide your build.gradle and config? I'm having java.lang.NoSuchMethodError: ch.qos.logback.core.util.Loader.getResources when trying to add testImplementation 'ch.qos.logback:logback-classic:1.2.3'

@cleemansen
Copy link

@almozavr snippet of my build.gradle (i'm sending my logs to papertrail w/ that setup):

    // cloud logging
    implementation 'org.slf4j:slf4j-api:1.7.25'
    implementation 'com.github.tony19:logback-android:1.3.0-3'
    implementation ('com.papertrailapp:logback-syslog4j:1.0.0') {
        exclude group: 'ch.qos.logback'
    }
    // better than nothing :/ https://github.com/tony19/logback-android/issues/151
    testImplementation 'ch.qos.logback:logback-classic:1.2.3'

My logback config is very specific. This setup "works" as described in my last comment w/ a simple console appender.

@almozavr
Copy link

I tried dependency substitution but gradle + android doesn't work that way 😖

dependenices {
    implementation "com.github.tony19:logback-android:$logbackAndroidVersion"
}
configurations.all { config ->
    if (config.name.toLowerCase().contains('test')) {
        println "Replacing module in configuration " + config.name
        config.resolutionStrategy.dependencySubstitution {
            substitute module("com.github.tony19:logback-android:$logbackAndroidVersion") with module("ch.qos.logback:logback-classic:$logbackVersion")
        }
    }
}

@almozavr
Copy link

@cleemansen I'm afraid, your solution is unreliable -> slf4j takes first "random" binding if there are multiple, it comes from their documentation, and actually I encountered it today..

@cleemansen
Copy link

I'm also not happy w/ that workaround.
Thanks for your "random" binding hint @almozavr! Did not recognized this behavior yet. But you need the logs the first time when the tests are red.. :/

I had the same idea substituting the libraries, too. Never tried it and I don't know if this approach is worthwhile.

@almozavr
Copy link

@cleemansen dep. substitution works great with vanilla gradle (executed from terminal), but it seems like Android Studio uses diff classpath rules, so it simple skips configuration rules for inbound test runs 😢

@tony19
Copy link
Owner

tony19 commented Feb 22, 2019

Local unit tests that use logback-android must be run with Robolectric, or else they'll hit this exception:

java.lang.RuntimeException: Method getExternalStorageState in android.os.Environment not mocked. See http://g.co/androidstudio/not-mocked for details.
  at android.os.Environment.getExternalStorageState(Environment.java)
  ...

If you cannot use Robolectric in your project for some reason, those tests must be moved to instrumented unit tests, which run on an emulator or device. See Android docs on testing for more information.

Setup:

  1. Add org.robolectric:robolectric to your app's build.gradle test dependencies:
dependencies {
  testImplementation 'org.robolectric:robolectric:3.8'
}
  1. Annotate your unit test class to run with with RobolectricTestRunner:
import org.junit.runner.RunWith;
import org.robolectric.RobolectricTestRunner;

@RunWith(RobolectricTestRunner.class)
public class ExampleUnitTest { ... }
  1. Add src/test/resources/assets/logback.xml with XML config. Note the resources subdirectory. Example config:
<configuration debug="true">
  <appender name="console" class="ch.qos.logback.core.ConsoleAppender">
    <encoder>
      <pattern>%msg</pattern>
    </encoder>
  </appender>

  <root level="DEBUG">
    <appender-ref ref="console" />
  </root>
</configuration>
  1. Add src/test/AndroidManifest.xml to silence a Robolectric warning. Alternatively, you could annotate your unit test class:
import org.robolectric.annotation.Config;

@Config(manifest = Config.NONE)
public class ExampleUnitTest { ... }

See changes in tony19-sandbox/logback-test-app@4682f5a

@almozavr
Copy link

@tony19 thanks for the so much detailed example! I believe it would be great to mention this sample app repo at readme or include in the main repo.

Also, I consider Robolectric is a valid option, I believe logback is not a best driver to stick with this framework, especially today when kotlintest or spek are so common. So if you gonna mention about android unit tests and robolectric, I'd propose to mention gradle's dependency substitution trick (see comments from above). If you need any assistance, I would be glad to help.

@tony19
Copy link
Owner

tony19 commented Feb 23, 2019

I believe it would be great to mention this sample app repo at readme or include in the main repo.

That repo is currently for CI only, but I might clean it up as a general sample app.

Also, I consider Robolectric is a valid option, I believe logback is not a best driver to stick with this framework, especially today when kotlintest or spek are so common

Robolectric is supplemental to unit test frameworks, such as kotlintest. It allows your unit tests to access Android classes (e.g., android.os.Environment) without running on an emulator or device. You could use Robolectric with kotlintest. Update: Turns out Robolectric requires JUnit 4, which kotlintest does not support any longer. As almozavr pointed out, Robolectric and kotlintest are not yet compatible.

@almozavr
Copy link

@tony19 I'm afraid, it's not that straightforward in the world of junit 4 and 5 and their runners, e.g.:
kotest/kotest#189

Anyway, I doubt one would stick with Robolectric to run vanilla unit tests just because of the logger tool..

@lock
Copy link

lock bot commented Mar 29, 2019

This thread has been automatically locked since there has not been any recent activity after it was closed. Please open a new issue for related bugs.

@lock lock bot added the archived label Mar 29, 2019
@lock lock bot locked as resolved and limited conversation to collaborators Mar 29, 2019
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

No branches or pull requests

4 participants