如何使用mockito模拟String?

时间:2009-07-03 12:55:34

标签: java unit-testing mocking mockito

我需要模拟一个测试场景,我在其中调用String对象的getBytes()方法,并得到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);
    }
}

15 个答案:

答案 0 :(得分:39)

问题是Java中的String类被标记为final,因此您无法使用传统的模拟框架进行模拟。根据{{​​3}},这也是该框架的限制。

答案 1 :(得分:13)

如何使用错误的编码名称创建String呢?参见

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

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

答案 2 :(得分:12)

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

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

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

答案 3 :(得分:7)

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

此外,您为A.f()的来电者带来同样的问题,因为A是最终的,f()是静态的。

这篇文章可能有助于说服你的同事减少100%的代码覆盖率:How to fail with 100% test coverage

答案 4 :(得分: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");
   }
}

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

答案 5 :(得分:3)

Mockito不能模拟最后的课程。 JMock与JDave的库结合在一起。 Here are instructions

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

答案 6 :(得分:3)

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

答案 7 :(得分:3)

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

答案 8 :(得分:1)

  

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

给定的覆盖目标是什么?有些人会说shooting for 100% coverage isn't always a good idea

此外,没有办法测试是否行使了一个捕获块。正确的方法是编写一个方法,该方法会引发异常,并使异常的观察结果成为成功标准。您可以通过添加“预期”值来使用JUnit的@Test注释执行此操作:

@Test(expected=IndexOutOfBoundsException.class) public void outOfBounds() {
   new ArrayList<Object>().get(1);
}

答案 9 :(得分:1)

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

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

答案 10 :(得分:1)

也许A.f(String)应该是A.f(CharSequence)。你可以模拟CharSequence。

答案 11 :(得分: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
    }
}

答案 12 :(得分: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,但你可以随心所欲地模仿。

答案 13 :(得分:0)

代替模拟字符串在设置方法中注入字符串值,如下所示

public void setup() throws IllegalAccessException {
    FieldUtils.writeField(yourTestClass, "stringVariableName", "value", true);
}

答案 14 :(得分:-1)

如果您的代码块实际上永远无法运行,并且管理要求具有100%的测试覆盖率,则必须更改某些内容。

你可以做的是让字符编码成为一个成员变量,并在你的类中添加一个package-private构造函数,让你传入它。在你的单元测试中,你可以调用新的构造函数,其中包含一个无意义的值。字符编码。