42

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

我试图使用以下代码来实现这一点:

String nonEncodedString = mock(String.class);
when(nonEncodedString.getBytes(anyString())).thenThrow(new UnsupportedEncodingException("Parsing error."));

问题是当我运行我的测试用例时,我得到一个 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.
            ...
        }
    }
}

这是我的测试类(我正在使用 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);
    }
}
4

15 回答 15

47

问题是StringJava中的类被标记为final,所以你不能模拟使用传统的模拟框架。根据Mockito FAQ,这也是该框架的限制。

于 2009-07-03T13:01:02.370 回答
13

只创建一个String带有错误编码名称的名称怎么样?看

public String(byte bytes[], int offset, int length, String charsetName)

嘲笑String几乎肯定是个坏主意。

于 2009-09-05T20:31:16.603 回答
13

如果您在 catch 块中要做的只是抛出一个运行时异常,那么您可以通过使用 Charset 对象来指定您的字符集名称来节省一些输入。

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

这样,您就不会因为编译器告诉您而捕获永远不会发生的异常。

于 2010-05-21T22:43:40.187 回答
7

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

此外,您给调用者带来了同样的问题,A.f()因为A它是最终的并且f()是静态的。

这篇文章可能有助于说服你的同事对 100% 的代码覆盖率不那么教条:如何在 100% 的测试覆盖率下失败

于 2009-12-03T02:32:28.813 回答
6

从其文档中,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");
   }
}

假设完整的“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);
      }
   }
}

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

我使用部分模拟@Mocked("getBytes")来防止 JMockit 模拟java.lang.String课堂上的所有内容(想象一下这会导致什么)。

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

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

于 2009-07-29T22:36:39.663 回答
3

Mockito 不能模拟最终课程。JMock,结合来自 JDave 的库即可。这里有说明

除了依赖 JDave 库来取消 JVM 中的所有内容之外,JMock 对 final 类没有做任何特别的事情,因此您可以尝试使用 JDave 的取消终结器,看看 Mockito 是否会模拟它。

于 2009-07-03T13:01:29.077 回答
3

您还可以使用 PowerMock 的 Mockito 扩展来模拟最终类/方法,即使在 String 等系统类中也是如此。但是,我也建议不要在这种情况下模拟 getBytes,而是尝试设置您的期望,以便使用填充了预期数据的实数字符串。

于 2009-12-21T09:52:31.817 回答
3

您将测试永远无法执行的代码。每个 Java VM 都需要 UTF-8 支持,请参阅http://java.sun.com/javase/6/docs/api/java/nio/charset/Charset.html

于 2009-12-21T09:57:23.410 回答
1

项目要求单元测试覆盖率必须但高于给定值。为了达到这样的覆盖百分比,测试必须覆盖相对于 UnsupportedEncodingException 的 catch 块。

给定的覆盖目标是什么?有人会说拍摄 100% 的覆盖率并不总是一个好主意

此外,这无法测试是否执行了 catch 块。正确的方法是编写一个导致异常被抛出的方法,并将抛出的异常作为成功标准的观察。您可以通过添加“预期”值来使用 JUnit 的 @Test 注释:

@Test(expected=IndexOutOfBoundsException.class) public void outOfBounds() {
   new ArrayList<Object>().get(1);
}
于 2009-07-03T15:10:47.517 回答
1

您是否尝试将无效的 charsetName 传递给 getBytes(String)?

您可以实现一个辅助方法来获取 charsetName,并在您的测试中将该方法覆盖为一个无意义的值。

于 2009-07-03T15:12:56.810 回答
1

也许 Af(String) 应该是 Af(CharSequence) 。你可以模拟一个 CharSequence。

于 2010-08-01T01:33:16.900 回答
0

如果您可以使用 JMockit,请查看 Rogério 的答案。

当且仅当您的目标是获得代码覆盖率而不是实际模拟缺少的 UTF-8 在运行时的样子时,您可以执行以下操作(并且您不能或不想使用 JMockit):

public static String f(String str){
    return f(str, "UTF-8");
}

// package private for example
static String f(String str, String charsetName){
    try {
        return new String(str.getBytes(charsetName));
    } catch (UnsupportedEncodingException e) {
        throw new IllegalArgumentException("Unsupported encoding: " + charsetName, e);
    }
}

public class TestA {

    @Test(expected=IllegalArgumentException.class)
    public void testInvalid(){
        A.f(str, "This is not the encoding you are looking for!");
    }

    @Test
    public void testNormal(){
        // TODO do the normal tests with the method taking only 1 parameter
    }
}
于 2017-02-03T03:12:37.770 回答
0

您可以更改方法以获取接口CharSequence

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

这样,你仍然可以传入 String,但你可以以任何你喜欢的方式模拟。

于 2018-01-12T05:37:49.247 回答
0

而不是在设置方法中模拟字符串注入字符串值,如下所示

public void setup() throws IllegalAccessException {
    FieldUtils.writeField(yourTestClass, "stringVariableName", "value", true);
}
于 2021-02-02T21:14:11.900 回答
-1

如果你有一个永远无法实际运行的代码块,并且管理要求有 100% 的测试覆盖率,那么就必须改变一些事情。

您可以做的是使字符编码成为成员变量,并向您的类添加一个包私有构造函数,让您将其传入。在您的单元测试中,您可以调用新的构造函数,为字符编码设置一个无意义的值.

于 2011-12-17T06:24:37.043 回答