【问题标题】:How to mock method e in Log如何在 Log 中模拟方法 e
【发布时间】:2016-04-22 07:20:24
【问题描述】:

这里 Utils.java 是我要测试的类,下面是 UtilsTest 类中调用的方法。 即使我正在模拟如下所示的 Log.e 方法

 @Before
  public void setUp() {
  when(Log.e(any(String.class),any(String.class))).thenReturn(any(Integer.class));
            utils = spy(new Utils());
  }

我收到以下异常

java.lang.RuntimeException: Method e in android.util.Log not mocked. See http://g.co/androidstudio/not-mocked for details.
    at android.util.Log.e(Log.java)
    at com.xxx.demo.utils.UtilsTest.setUp(UtilsTest.java:41)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    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.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.runners.ParentRunner.run(ParentRunner.java:363)
    at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
    at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:78)
    at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:212)
    at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:68)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
    at com.intellij.rt.execution.application.AppMain.main(AppMain.java:140)

【问题讨论】:

    标签: android junit mockito


    【解决方案1】:

    这对我有用。我只使用 JUnit,并且能够非常轻松地模拟 Log没有任何第三方库。只需在app/src/test/java/android/util 内创建一个文件Log.java,内容如下:

    package android.util; 
    
    public class Log {
        public static int d(String tag, String msg) {
            System.out.println("DEBUG: " + tag + ": " + msg);
            return 0;
        }
    
        public static int i(String tag, String msg) {
            System.out.println("INFO: " + tag + ": " + msg);
            return 0;
        }
    
        public static int w(String tag, String msg) {
            System.out.println("WARN: " + tag + ": " + msg);
            return 0;
        }
    
        public static int e(String tag, String msg) {
            System.out.println("ERROR: " + tag + ": " + msg);
            return 0;
        }
    
        // add other methods if required...
    }
    

    【讨论】:

    • 这真是太棒了。它避免了对 PowerMockito 的需求。 10/10
    • 很好的答案,我的理论是如果您必须在单元测试中使用 Mock API,那么您的代码组织得不够好,无法进行单元测试。如果您使用外部库,则使用带有运行时和真实对象的集成测试。在我所有的 Android 应用程序中,我创建了一个包装类 LogUtil,它基于标志启用日志,这有助于我避免模拟 Log 类并使用标志启用/禁用日志。在生产中,无论如何我都会删除所有带有 progaurd 的日志语句。
    • @MGDevelopert 你是对的。 IMO 应该很少使用这种技术/技巧。例如,我只对 Log 类这样做,因为它太普遍了,并且到处传递 Log 包装器会降低代码的可读性。在大多数情况下,应该使用依赖注入。
    • 效果很好。在复制粘贴之前,添加包名:package android.util;
    • @DavidKennedy 使用 @file:JvmName("Log") 和顶级函数。
    【解决方案2】:

    你可以把它放到你的 gradle 脚本中:

    android {
       ...
       testOptions { 
           unitTests.returnDefaultValues = true
       }
    }
    

    这将决定来自 android.jar 的非模拟方法是否应该抛出异常或返回默认值。

    【讨论】:

    • 来自文档: 警告: 将 returnDefaultValues 属性设置为 true 时应该小心。空/零返回值可能会在您的测试中引入回归,这很难调试并且可能允许失败的测试通过。 仅将其用作最后的手段。
    • unitTests 变量名已更改为isReturnDefaultValues:developer.android.com/reference/tools/gradle-api/4.1/com/…
    【解决方案3】:

    如果使用 Kotlin,我建议使用像 mockk 这样的现代库,它内置了对静态和许多其他东西的处理。然后可以这样做:

    mockkStatic(Log::class)
    every { Log.v(any(), any()) } returns 0
    every { Log.d(any(), any()) } returns 0
    every { Log.i(any(), any()) } returns 0
    every { Log.e(any(), any()) } returns 0
    

    【讨论】:

    • 很好的介绍+1,测试通过但错误已经报告!
    • 如果要捕获Log.w添加:every { Log.w(any(), any<String>()) } returns 0
    • 似乎不适用于Log.wtf (every { Log.wtf(any(), any<String>()) } returns 0):编译失败并出现错误:Unresolved reference: wtf。 IDE lint 在代码中什么也没说。有什么想法吗?
    • Brilliant +1 !!... 这在我使用 Mockk 时很有效。
    • 我可以使用 mockk 调用 Log.* 使用 println() 来输出预期的日志吗?
    【解决方案4】:

    使用PowerMockito

    @RunWith(PowerMockRunner.class)
    @PrepareForTest({Log.class})
    public class TestsToRun() {
        @Test
        public void test() {
            PowerMockito.mockStatic(Log.class);
        }
    }
    

    你可以走了。请注意,PowerMockito 不会自动模拟继承的静态方法,因此如果要模拟扩展 Log 的自定义日志记录类,您仍然必须模拟 Log 以进行诸如 MyCustomLog.e() 之类的调用。

    【讨论】:

    • 你是如何在 Gradle 中获得 PowerMockRunner 的??
    • @IgorGanapolsky 查看我的回答here
    • 查看我在 Kotlin here 中模拟 Log.e 和 Log.println 的答案
    • PowerMockito 在 2019 年仍然是 Kotiln 的流行解决方案吗?或者我们应该看看其他模拟库(即 MockK)。
    【解决方案5】:

    感谢@Paglian 的回答和@Miha_x64 的评论,我能够为 kotlin 做同样的事情。

    app/src/test/java/android/util中添加如下Log.kt文件

    @file:JvmName("Log")
    
    package android.util
    
    fun e(tag: String, msg: String, t: Throwable): Int {
        println("ERROR: $tag: $msg")
        return 0
    }
    
    fun e(tag: String, msg: String): Int {
        println("ERROR: $tag: $msg")
        return 0
    }
    
    fun w(tag: String, msg: String): Int {
        println("WARN: $tag: $msg")
        return 0
    }
    
    // add other functions if required...
    

    瞧,您对 Log.xxx 的调用应该调用这些函数。

    【讨论】:

      【解决方案6】:

      使用 PowerMockito。

      @RunWith(PowerMockRunner.class)
      @PrepareForTest({ClassNameOnWhichTestsAreWritten.class , Log.class})
      public class TestsOnClass() {
          @Before
          public void setup() {
              PowerMockito.mockStatic(Log.class);
          }
          @Test
          public void Test_1(){
      
          }
          @Test
          public void Test_2(){
      
          }
       }
      

      【讨论】:

      • 值得一提的是,由于一个错误,对于 JUnit 4.12,使用 PowerMock >= 1.6.1。否则,尝试使用 JUnit 4.11 运行
      【解决方案7】:

      使用PowerMock 可以从 Android 记录器模拟 Log.i/e/w 静态方法。当然,理想情况下,您应该创建一个日志接口或外观,并提供一种将日志记录到不同来源的方法。

      这是一个完整的 Kotlin 解决方案:

      import org.powermock.modules.junit4.PowerMockRunner
      import org.powermock.api.mockito.PowerMockito
      import org.powermock.core.classloader.annotations.PrepareForTest
      
      /**
       * Logger Unit tests
       */
      @RunWith(PowerMockRunner::class)
      @PrepareForTest(Log::class)
      class McLogTest {
      
          @Before
          fun beforeTest() {
              PowerMockito.mockStatic(Log::class.java)
              Mockito.`when`(Log.i(any(), any())).then {
                  println(it.arguments[1] as String)
                  1
              }
          }
      
          @Test
          fun logInfo() {
              Log.i("TAG1,", "This is a samle info log content -> 123")
          }
      }
      

      记得在gradle中添加依赖:

      dependencies {
          testImplementation "junit:junit:4.12"
          testImplementation "org.mockito:mockito-core:2.15.0"
          testImplementation "io.kotlintest:kotlintest:2.0.7"
          testImplementation 'org.powermock:powermock-module-junit4-rule:2.0.0-beta.5'
          testImplementation 'org.powermock:powermock-core:2.0.0-beta.5'
          testImplementation 'org.powermock:powermock-module-junit4:2.0.0-beta.5'
          testImplementation 'org.powermock:powermock-api-mockito2:2.0.0-beta.5'
      }
      

      模拟Log.println 方法使用:

      Mockito.`when`(Log.println(anyInt(), any(), any())).then {
          println(it.arguments[2] as String)
          1
      }
      

      【讨论】:

      • 这在 Java 中也有可能吗?
      • @Bowi:请参阅我在 Java 中使用 system.out.println 模拟 Log.v 的解决方案,它也适用于 JDK11 stackoverflow.com/a/63642300/3569768
      【解决方案8】:

      我建议您使用timber 进行日志记录。

      虽然它在运行测试时不会记录任何内容,但它不会像 android Log 类那样不必要地让你的测试失败。 Timber 让您可以方便地控制应用的调试和生产构建。

      【讨论】:

        【解决方案9】:

        另一个解决方案是使用 Robolectric。想试试看its setup

        在你的模块的 build.gradle 中,添加以下内容

        testImplementation "org.robolectric:robolectric:3.8"
        
        android {
          testOptions {
            unitTests {
              includeAndroidResources = true
            }
          }
        }
        

        在你的测试课中,

        @RunWith(RobolectricTestRunner.class)
        public class SandwichTest {
          @Before
          public void setUp() {
          }
        }
        

        在较新版本的 Robolectric(使用 4.3 测试)中,您的测试类应如下所示:

        @RunWith(RobolectricTestRunner.class)
        @Config(shadows = ShadowLog.class)
        public class SandwichTest {
            @Before
            public void setUp() {
                ShadowLog.setupLogging();
            }
        
            // tests ...
        }
        

        【讨论】:

        • 不知道为什么这不是公认的答案。如果您正在测试 Android 代码,您不会模拟每个 Android 调用。那是不切实际的。您如上所述使用 Robolectric。 Robolectric 提供了 Android SDK 的实现。有时可能很难从 Robolectric 的实施中获得什么期望,但它是可控的。
        【解决方案10】:

        @Paglian 答案的 kotlin 版本,无需为 JUnit 测试模拟 android.util.Log :)

        强调:

        1 -> 顶部的包名

        2 -> 函数顶部的注释

        package android.util
        
        class Log {
            companion object {
                fun d(tag: String, msg: String): Int {
                    println("DEBUG: $tag: $msg")
                    return 0
                }
        
                @JvmStatic
                fun i(tag: String, msg: String): Int {
                    println("INFO: $tag: $msg")
                    return 0
                }
        
                @JvmStatic
                fun w(tag: String, msg: String): Int {
                    println("WARN: $tag: $msg")
                    return 0
                }
        
                @JvmStatic
                fun w(tag: String, msg: String, exception: Throwable): Int {
                    println("WARN: $tag: $msg , $exception")
                    return 0
                }
        
                @JvmStatic
                fun e(tag: String, msg: String): Int {
                    println("ERROR: $tag: $msg")
                    return 0
                }
            }
        }
        

        【讨论】:

          【解决方案11】:

          Mockito 不模拟静态方法。在顶部使用 PowerMockito。 Here 就是一个例子。

          【讨论】:

          • @user3762991 您还需要更改匹配器。您不能在 thenReturn(...) 语句中使用匹配器。您需要指定有形价值。查看更多信息here
          • 如果e,d,v方法不能被mock,只是因为这个限制,mockito会变得不可用吗?
          • 如果你不能吃叉子,它会变得无法使用吗?它只是有另一个目的。
          【解决方案12】:

          如果您使用的是 org.slf4j.Logger,那么只需使用 PowerMockito 在测试类中模拟 Logger 即可。

          @RunWith(PowerMockRunner.class)
          public class MyClassTest {
          
          @Mock
          Logger mockedLOG;
          
          ...
          }
          

          【讨论】:

            【解决方案13】:

            扩展kosiara 的答案,在 Java 中使用 PowerMockMockitoJDK11 来模拟android.Log.v 方法和 System.out.println 用于 Android Studio 4.0.1 中的单元测试。

            这是一个完整的Java解决方案:

            import android.util.Log;
            import org.junit.Before;
            import org.junit.Test;
            import org.junit.runner.RunWith;
            import org.mockito.Mockito;
            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.ArgumentMatchers.any;
            
            @RunWith(PowerMockRunner.class)
            @PrepareForTest(Log.class)
            public class MyLogUnitTest {
                @Before
                public void setup() {
                    // mock static Log.v call with System.out.println
                    PowerMockito.mockStatic(Log.class);
                    Mockito.when(Log.v(any(), any())).then(new Answer<Void>() {
                        @Override
                        public Void answer(InvocationOnMock invocation) throws Throwable {
                            String TAG = (String) invocation.getArguments()[0];
                            String msg = (String) invocation.getArguments()[1];
                            System.out.println(String.format("V/%s: %s", TAG, msg));
                            return null;
                        }
                    });
                }
            
                @Test
                public void logV() {
                    Log.v("MainActivity", "onCreate() called!");
                }
            
            }
            

            记得在你的单元测试所在的 module build.gradle 文件中添加依赖项:

            dependencies {
                ...
            
                /* PowerMock android.Log for OpenJDK11 */
                def mockitoVersion =  "3.5.7"
                def powerMockVersion = "2.0.7"
                // optional libs -- Mockito framework
                testImplementation "org.mockito:mockito-core:${mockitoVersion}"
                // optional libs -- power mock
                testImplementation "org.powermock:powermock-module-junit4:${powerMockVersion}"
                testImplementation "org.powermock:powermock-api-mockito2:${powerMockVersion}"
                testImplementation "org.powermock:powermock-module-junit4-rule:${powerMockVersion}"
                testImplementation "org.powermock:powermock-module-junit4-ruleagent:${powerMockVersion}"
            }
            

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 1970-01-01
              • 2019-08-07
              • 2015-08-23
              • 2021-05-26
              • 1970-01-01
              • 1970-01-01
              • 2020-03-31
              • 2011-09-10
              相关资源
              最近更新 更多