我需要模拟一个测试场景,我在其中调用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);
}
}
答案 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构造函数,让你传入它。在你的单元测试中,你可以调用新的构造函数,其中包含一个无意义的值。字符编码。