如何使用mockito模拟String?

Alc*_*sta 38 java unit-testing mocking mockito

我需要模拟一个测试场景,我在其中调用getBytes()String对象的方法,并得到UnsupportedEncodingException.

我试图使用以下代码实现:

String nonEncodedString = mock(String.class);
when(nonEncodedString.getBytes(anyString())).thenThrow(new UnsupportedEncodingException("Parsing error."));
Run Code Online (Sandbox Code Playgroud)

问题是,当我运行我的测试用例时,我得到一个MockitoException,表示我无法模拟java.lang.String类.

有没有办法使用mockito模拟String对象,或者,当我调用getBytes方法时,一种方法使我的String对象抛出UnsupportedEncodingException?


以下是更多细节来说明问题:

这是我要测试的类:

public final class A {
    public static String f(String str){
        try {
            return new String(str.getBytes("UTF-8"));
        } catch (UnsupportedEncodingException e) {
            // This is the catch block that I want to exercise.
            ...
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

这是我的测试类(我使用的是JUnit 4和mockito):

public class TestA {

    @Test(expected=UnsupportedEncodingException.class)
    public void test(){
        String aString = mock(String.class);
        when(nonEncodedString.getBytes(anyString())).thenThrow(new UnsupportedEncodingException("Parsing error."));
        A.f(aString);
    }
}
Run Code Online (Sandbox Code Playgroud)

Pet*_*ter 39

问题是StringJava中的类被标记为final,因此您无法使用传统的模拟框架进行模拟.根据Mockito FAQ,这也是该框架的限制.

  • 使用 Mockito 2,您可以[模拟最终课程](/sf/answers/2801280681/)。 (2认同)

Ste*_*man 13

如何String使用错误的编码名称创建一个?看到

public String(byte bytes[], int offset, int length, String charsetName)
Run Code Online (Sandbox Code Playgroud)

模拟String几乎肯定是一个坏主意.


小智 13

如果您要在catch块中执行的操作是抛出运行时异常,那么只需使用Charset对象来指定您的字符集名称,就可以节省一些输入.

public final class A{
    public static String f(String str){
        return new String(str.getBytes(Charset.forName("UTF-8")));
    }
}
Run Code Online (Sandbox Code Playgroud)

这样你就不会捕获一个永远不会因为编译器告诉你而发生的异常.


Jas*_*son 7

正如其他人所指出的那样,你不能用Mockito来模拟最后一堂课.但是,更重要的一点是测试不是特别有用,因为它只是证明String.getBytes()可以抛出异常,这显然可以做到.如果您对测试此功能感到强烈,我想您可以为编码添加参数f()并将错误值发送到测试中.

此外,您导致调用者的相同问题A.f()因为A是最终的并且f()是静态的.

本文可能有助于说服您的同事减少100%的代码覆盖率:如何以100%的测试覆盖率失败.


Rog*_*rio 5

从其文档中,JDave无法从引导类加载器加载的类中删除"final"修饰符.这包括所有JRE类(来自java.lang,java.util等).

一个让你模拟任何东西的工具是JMockit.

使用JMockit,您的测试可以写成:

import java.io.*;
import org.junit.*;
import mockit.*;

public final class ATest
{
   @Test(expected = UnsupportedOperationException.class)
   public void test() throws Exception
   {
      new Expectations()
      {
         @Mocked("getBytes")
         String aString;

         {
            aString.getBytes(anyString);
            result = new UnsupportedEncodingException("Parsing error.");
         }
      };

      A.f("test");
   }
}
Run Code Online (Sandbox Code Playgroud)

假设完整的"A"类是:

import java.io.*;

public final class A
{
   public static String f(String str)
   {
      try {
         return new String(str.getBytes("UTF-8"));
      }
      catch (UnsupportedEncodingException e) {
         throw new UnsupportedOperationException(e);
      }
   }
}
Run Code Online (Sandbox Code Playgroud)

我实际上在我的机器上执行了这个测试.(注意,我在运行时异常中包装了原始的已检查异常.)

我使用了部分模拟@Mocked("getBytes")来阻止JMockit模拟java.lang.String类中的所有内容(想象一下这可能导致什么).

现在,这个测试确实是不必要的,因为"UTF-8"是一个标准的字符集,需要在所有JRE中得到支持.因此,在生产环境中,永远不会执行catch块.

但是,"需要"或希望覆盖捕获块仍然有效.那么,如何在不降低覆盖率的情况下摆脱测试呢?这是我的想法:assert false;在catch块中插入一行作为第一个语句,并在报告覆盖率度量时让代码覆盖率工具忽略整个catch块.这是JMockit Coverage的"TODO项目"之一.8 ^)