如何使用mockito模拟字符串?

发布于 2024-07-25 16:14:04 字数 1177 浏览 5 评论 0原文

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

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

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

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

有没有办法使用mockito来模拟String对象,或者让我的String对象在调用getBytes方法时抛出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);
    }
}

I need to simulate a test scenario in which I call the getBytes() method of a String object and I get an UnsupportedEncodingException.

I have tried to achieve that using the following code:

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

The problem is that when I run my test case I get a MockitoException that says that I can't mock a java.lang.String class.

Is there a way to mock a String object using mockito or, alternatively, a way to make my String object throw an UnsupportedEncodingException when I call the getBytes method?


Here are more details to illustrate the problem:

This is the class that I want to test:

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.
            ...
        }
    }
}

This is my testing class (I'm using JUnit 4 and 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);
    }
}

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(16

巴黎夜雨 2024-08-01 16:14:05

您无法使用 Mockito 模拟 String。 这与它是 final 无关,因为 Mockito 从 2.0 开始就能够模拟 Final 类,并且从 5.0 开始就能够原生模拟 Final 类。

如果您尝试使用最新版本,您会收到此 MockitoException

无法模拟/监视类 java.lang.String
Mockito 无法模拟/间谍,因为:
- 无法模拟包装类型、String.class 或 Class.class

这是设计的

这正在按预期工作。 你不能模拟字符串。 请改用真实的字符串。

You cannot mock String with Mockito. It has nothing to do with it being final, as Mockito has been able to mock final classes since 2.0, and natively since 5.0.

If you attempt it with the latest version, you get this MockitoException:

Cannot mock/spy class java.lang.String
Mockito cannot mock/spy because :
- Cannot mock wrapper types, String.class or Class.class

This is by design.

This is working as intended. You can't mock strings. Use a real string instead.

物价感观 2024-08-01 16:14:05

如果您可以使用 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
    }
}

If you can use JMockit, look at Rogério answer.

If and only if your goal is to get code coverage but not actually simulate what missing UTF-8 would look like at runtime you can do the following (and that you can't or don't want to use 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
    }
}
如果没有 2024-08-01 16:14:05

您可以更改方法以采用接口 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,但您可以以任何您喜欢的方式进行模拟。

You can change your method to take the interface 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.
            ...
        }
    }
}

That way, you can still pass in String, but you can mock any way you like.

吹泡泡o 2024-08-01 16:14:05

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

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

instead of mocking string inject string value at setup method as below

public void setup() throws IllegalAccessException {
    FieldUtils.writeField(yourTestClass, "stringVariableName", "value", true);
}
悲歌长辞 2024-08-01 16:14:05

如果您有一段永远无法实际运行的代码块,并且管理要求具有 100% 的测试覆盖率,那么就必须进行某些更改。

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

If you have a block of code that can never actually be run, and a managerial requirement to have 100% test coverage, then something's going to have to change.

What you could do is make the character encoding a member variable, and add a package-private constructor to your class that lets you pass it in. In your unit test, you could call the new constructor, with a nonsense value for the character encoding.

So尛奶瓶 2024-08-01 16:14:04

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

The problem is the String class in Java is marked as final, so you cannot mock is using traditional mocking frameworks. According to the Mockito FAQ, this is a limitation of that framework as well.

尾戒 2024-08-01 16:14:04

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

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

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

How about just creating a String with a bad encoding name? See

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

Mocking String is almost certainly a bad idea.

街道布景 2024-08-01 16:14:04

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

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

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

If all you are going to do in your catch block is throw a runtime exception then you can save yourself some typing by just using a Charset object to specify your character set name.

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

This way you aren't catching an exception that will never happen just because the compiler tells you to.

很酷不放纵 2024-08-01 16:14:04

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

此外,您还会给 Af() 的调用者带来同样的问题,因为 A 是最终的,而 f() 是静态的。

本文可能有助于说服您的同事不要那么教条地对待 100% 代码覆盖率:如何在 100% 测试覆盖率下失败

As others have indicated, you can't use Mockito to mock a final class. However, the more important point is that the test isn't especially useful because it's just demonstrating that String.getBytes() can throw an exception, which it can obviously do. If you feel strongly about testing this functionality, I guess you could add a parameter for the encoding to f() and send a bad value into the test.

Also, you are causing the same problem for the caller of A.f() because A is final and f() is static.

This article might be useful in convincing your coworkers to be less dogmatic about 100% code coverage: How to fail with 100% test coverage.

吲‖鸣 2024-08-01 16:14:04

从其文档来看,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< 中的所有内容。 /code> class (想象一下这会导致什么)。

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

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

From its documentation, JDave can't remove "final" modifiers from classes loaded by the bootstrap classloader. That includes all JRE classes (from java.lang, java.util, etc.).

A tool that does let you mock anything is JMockit.

With JMockit, your test can be written as:

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");
   }
}

assuming that the complete "A" class is:

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);
      }
   }
}

I actually executed this test in my machine. (Notice I wrapped the original checked exception in a runtime exception.)

I used partial mocking through @Mocked("getBytes") to prevent JMockit from mocking everything in the java.lang.String class (just imagine what that could cause).

Now, this test really is unnecessary, because "UTF-8" is a standard charset, required to be supported in all JREs. Therefore, in a production environment the catch block will never be executed.

The "need" or desire to cover the catch block is still valid, though. So, how to get rid of the test without reducing the coverage percentage? Here is my idea: insert a line with assert false; as the first statement inside the catch block, and have the Code Coverage tool ignore the whole catch block when reporting coverage measures. This is one of my "TODO items" for JMockit Coverage. 8^)

单调的奢华 2024-08-01 16:14:04

Mockito 无法模拟期末课程。 JMock 可以与 JDave 的库结合起来。 这里是说明

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

Mockito can't mock final classes. JMock, combined with a library from JDave can. Here are instructions.

JMock doesn't do anything special for final classes other than rely on the JDave library to unfinalize everything in the JVM, so you could experiment with using JDave's unfinalizer and see if Mockito will then mock it.

最舍不得你 2024-08-01 16:14:04

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

You can also use PowerMock's Mockito extension to mock final classes/methods even in system classes such as String. However I would also advice against mocking getBytes in this case and rather try to setup your expectation so that a real is String populated with the expected data is used instead.

撩发小公举 2024-08-01 16:14:04

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

You will be testing code that can never be executed. UTF-8 support is required to be in every Java VM, see http://java.sun.com/javase/6/docs/api/java/nio/charset/Charset.html

甜柠檬 2024-08-01 16:14:04

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

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

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

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

It is a project requirement that the unit tests coverage percentage must but higher than a given value. To achieve such percentage of coverage the tests must cover the catch block relative to the UnsupportedEncodingException.

What is that given coverage target? Some people would say that shooting for 100% coverage isn't always a good idea.

Besides, that's no way to test whether or not a catch block was exercised. The right way is to write a method that causes the exception to be thrown and make observation of the exception being thrown the success criterion. You do this with JUnit's @Test annotation by adding the "expected" value:

@Test(expected=IndexOutOfBoundsException.class) public void outOfBounds() {
   new ArrayList<Object>().get(1);
}
三寸金莲 2024-08-01 16:14:04

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

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

Have you tried passing an invalid charsetName to getBytes(String)?

You could implement a helper method to get the charsetName, and override that method within your test to a nonsense value.

剧终人散尽 2024-08-01 16:14:04

也许 Af(String) 应该改为 Af(CharSequence) 。 您可以模拟 CharSequence。

Perhaps A.f(String) should be A.f(CharSequence) instead. You can mock a CharSequence.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文