【问题标题】:Need help to write a unit test using Mockito and JUnit4需要帮助来使用 Mockito 和 JUnit4 编写单元测试
【发布时间】:2016-03-03 04:35:49
【问题描述】:

在使用 Mockito 和 JUnit4 为以下代码编写单元测试时需要帮助,

public class MyFragmentPresenterImpl { 
      public Boolean isValid(String value) {
        return !(TextUtils.isEmpty(value));
      }
}

我尝试了以下方法: MyFragmentPresenter mMyFragmentPresenter

@Before
public void setup(){
    mMyFragmentPresenter=new MyFragmentPresenterImpl();
}

@Test
public void testEmptyValue() throws Exception {
    String value=null;
    assertFalse(mMyFragmentPresenter.isValid(value));
}

但它返回以下异常,

java.lang.RuntimeException: android.text.TextUtils 中的方法 isEmpty 没有被嘲笑。有关详细信息,请参阅http://g.co/androidstudio/not-mocked。在 android.text.TextUtils.isEmpty(TextUtils.java) at ....

【问题讨论】:

    标签: android mockito junit4


    【解决方案1】:

    由于 JUnit TestCase 类不能使用 Android 相关的 API,我们必须 Mock 它。
    使用PowerMockito 模拟静态类。

    在您的测试用例类上方添加两行,

    @RunWith(PowerMockRunner.class)
    @PrepareForTest(TextUtils.class)
    public class YourTest
    {
    
    }
    

    还有设置代码

    @Before
    public void setup() {
        PowerMockito.mockStatic(TextUtils.class);
        PowerMockito.when(TextUtils.isEmpty(any(CharSequence.class))).thenAnswer(new Answer<Boolean>() {
            @Override
            public Boolean answer(InvocationOnMock invocation) throws Throwable {
                CharSequence a = (CharSequence) invocation.getArguments()[0];
                return !(a != null && a.length() > 0);
            }
        });
    }
    

    用我们自己的逻辑实现TextUtils.isEmpty()

    另外,在app.gradle 文件中添加依赖项。

    testCompile "org.powermock:powermock-module-junit4:1.6.2"
    testCompile "org.powermock:powermock-module-junit4-rule:1.6.2"
    testCompile "org.powermock:powermock-api-mockito:1.6.2"
    testCompile "org.powermock:powermock-classloading-xstream:1.6.2"
    

    感谢BehelitException 的回答。

    【讨论】:

    • 这仅部分有效。我认为这在调用 TextUtils.isEmpty(null) 时不起作用,因为它与 when 语句不匹配。请参阅我的答案以获得更好的解决方案
    【解决方案2】:

    使用 PowerMockito

    在您的班级名称上方添加此内容,并包括任何其他 CUT 班级名称(正在测试的班级)

    @RunWith(PowerMockRunner.class)
    @PrepareForTest({TextUtils.class})
    public class ContactUtilsTest
    {
    

    将此添加到您的@Before

    @Before
    public void setup(){
        PowerMockito.mockStatic(TextUtils.class);
        mMyFragmentPresenter=new MyFragmentPresenterImpl();
    }
    

    这将使 PowerMockito 返回 TextUtils 中方法的默认值

    您还必须添加相关的 gradle 依赖项

    testCompile "org.powermock:powermock-module-junit4:1.6.2"
    testCompile "org.powermock:powermock-module-junit4-rule:1.6.2"
    testCompile "org.powermock:powermock-api-mockito:1.6.2"
    testCompile "org.powermock:powermock-classloading-xstream:1.6.2"
    

    【讨论】:

      【解决方案3】:

      这是@Exception 提到的一个已知问题。就我而言,我也偶然发现了同样的情况,但根据高级开发人员的建议,我决定使用Strings.isNullOrEmpty() 而不是TextUtils.isEmpty()。事实证明这是避免它的好方法。

      更新:我最好提一下这个实用函数Strings.isNullOrEmpty() 需要 Guava 库。

      【讨论】:

        【解决方案4】:

        这是一个已知问题,因为 Android 测试基础中的一个条款说:

        您可以使用 JUnit TestCase 类对类进行单元测试 不调用 Android API。

        使用LogTextUtils 等类时,默认行为会出现问题。

        总结一下:

        1. android.jar 之前是 mock 的,所以某些 Android API 返回值可能不符合预期。
        2. JUnit 本身是对 java 代码的单一度量,所以尽量不要使用 Android API 方法。

        来源: http://www.liangfeizc.com/2016/01/28/unit-test-on-android/

        【讨论】:

          【解决方案5】:

          如果是 Android Studio,请在 gradle 文件中添加这一行。

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

          【讨论】:

          • 默认值为 true。将导致 textUtils 始终返回 true。但是在某些情况下,您可能会测试一个虚假案例。检查分支的测试覆盖将错过错误的情况。
          【解决方案6】:

          您应该使用 Robolectric:

          testImplementation "org.robolectric:robolectric:3.4.2"
          

          然后

          @RunWith(RobolectricTestRunner::class)
          class TestClass {
              ...
          }
          

          【讨论】:

          • 虽然这在很大程度上是正确的,但您不一定应该使用 Robolectric,它只是可以,因为并非所有应用程序都可以使用 Robolectric (例如当你使用 SQLCipher)
          【解决方案7】:

          我能够通过运行以下测试类来解决此错误。

          @RunWith(RobolectricGradleTestRunner.class)
          public class MySimpleTest {
          .....a bunch of test cases
          }
          

          此 wiki 页面更详细地解释 https://github.com/yahoo/squidb/wiki/Unit-testing-with-model-objects

          【讨论】:

            【解决方案8】:

            我用这个替换了我的项目TextUtils.isEmpty(...) 中的所有地方:

            /**
             * Util class to be used instead of Android classes for Junit tests.
             */
            public class Utils {
            
                /**
                 * Returns true if the string is null or 0-length.
                 * @param str the string to be examined
                 * @return true if str is null or zero length
                 */
                public static boolean isEmpty(@Nullable CharSequence str) {
                    return str == null || str.length() == 0;
                }
            }
            

            【讨论】:

              【解决方案9】:

              作为约翰尼的回答的后续行动,要捕获 TextUtils.isEmpty(null) 调用,您可以使用这段代码。

              PowerMockito.mockStatic(TextUtils.class);
              PowerMockito.when(TextUtils.isEmpty(any()))
                  .thenAnswer((Answer<Boolean>) invocation -> {
                      Object s = invocation.getArguments()[0];
                      return s == null || s.length() == 0;
                  });
              

              【讨论】:

              • 以上内容有所帮助,但要绝对正确,您需要做一个额外的检查,如 (CharSequence)s).length 或 kotlin s == null || (s 是 CharSequence && s.length == 0)
              • 我采纳了你的想法并完成了它,就像-PowerMockito.when(TextUtils.isEmpty(any())).thenAnswer { invocation -&gt; val str = invocation.arguments[0] return@thenAnswer str == null || (str is String &amp;&amp; str.length == 0) }
              【解决方案10】:

              解决方案 1:

              我想提供一个 Kotlin 和一个 Java 版本。

              Kotlin 版本:

              import android.text.TextUtils
              
              import org.junit.Before
              
              import org.junit.runner.RunWith
              
              import org.mockito.Matchers.any
              
              import org.powermock.api.mockito.PowerMockito
              

              导入 org.powermock.core.classloader.annotations.PrepareForTest

              import org.powermock.modules.junit4.PowerMockRunner
              
              
              
              @RunWith(PowerMockRunner::class)
              
              @PrepareForTest(TextUtils::class)
              
              class UserOwnedDataTest1 {
              
              
              
                  @Before
              
                  fun setup() {
              
                      PowerMockito.mockStatic(TextUtils::class.java)
              
                      PowerMockito.`when`(TextUtils.isEmpty(any(CharSequence::class.java))).thenAnswer { invocation ->
              
                          val a = invocation.arguments[0] as? CharSequence
              
                         a?.isEmpty() ?: true
              
                      }
              
                  }
              
              }
              

              Java 版本:

              import android.text.TextUtils;
              
              
              
              import org.junit.Before;
              
              import org.junit.runner.RunWith;
              
              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.any;
              
              
              
              @RunWith(PowerMockRunner.class)
              
              @PrepareForTest(TextUtils.class)
              
              public final class UserOwnedDataTest2 {
              
              
              
                  @Before
              
                  public void setup() {
              
                      PowerMockito.mockStatic(TextUtils.class);
              
                      PowerMockito.when(TextUtils.isEmpty(any(CharSequence.class))).thenAnswer((Answer<Boolean>) invocation -> {
              
                          CharSequence a = (CharSequence) invocation.getArguments()[0];
              
                          return !(a != null && a.length() > 0);
              
                      });
              
                  }
              
              }
              

              不要忘记添加依赖项:

              testCompile "org.powermock:powermock-module-junit4:1.6.2"
              
              testCompile "org.powermock:powermock-module-junit4-rule:1.6.2"
              
              testCompile "org.powermock:powermock-api-mockito:1.6.2"
              
              testCompile "org.powermock:powermock-classloading-xstream:1.6.2"
              

              我记得我们还需要另一个依赖项,但不清楚。

              无论如何,您都可以轻松修复丢失的依赖项。

              解决方案 2:

              或者您可以使用 TextUtils 添加相同的包和类名

              package android.text;
              
              
              
              public class TextUtils {
              
                  public static boolean isEmpty( CharSequence str) {
              
                      return str == null || str.length() == 0;
              
                  }
              
              }
              

              【讨论】:

                【解决方案11】:

                我有更新的 power mock (2.0.9) 和 mockito (3.9.0)。将执行更改为这个:

                when(TextUtils.isEmpty(any())).thenAnswer((Answer<Boolean>) invocation -> {
                    CharSequence a = (CharSequence) invocation.getArguments()[0];
                    return a == null || a.length() == 0;
                });
                

                【讨论】: