模拟 MessageDigest.getInstance() 抛出异常

发布于 2024-11-08 00:10:49 字数 330 浏览 1 评论 0原文

我得到了以下方法:

private MessageDigest getMessageDigest() {
    try {
        return MessageDigest.getInstance("MD5");
    } catch (NoSuchAlgorithmException e) {
        throw new Error(e);
    }
}

为了获得 100% 的代码覆盖率,我需要进入 catch 块。但我绝对不知道如何才能做到这一点。在这种情况下有一些模拟框架可以帮助我吗?如果是这样 - 怎么办?或者还有另一种方法而不必捕获异常吗?

I got the following method:

private MessageDigest getMessageDigest() {
    try {
        return MessageDigest.getInstance("MD5");
    } catch (NoSuchAlgorithmException e) {
        throw new Error(e);
    }
}

To get 100% code coverage I need to get into the catch block. But I am absolutely not sure how I can do that. Is there some mocking framework that could help me in this case? If so - how? Or is there even another way without having to catch an exception?

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

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

发布评论

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

评论(8

心舞飞扬 2024-11-15 00:10:49

MessageDigest 上的 getInstance 方法看起来像一个静态方法。静态方法不能被模拟。我同意 Ratchet 的观点,即您不应该以 100% 的代码覆盖率为目标,而应该专注于测试具有复杂代码的区域。

The getInstance method on MessageDigest looks like a static method. Static methods cannot be mocked. I agree with ratchet that you should not aim for 100 % code coverage but focus on testing the areas with complex code instead.

↙厌世 2024-11-15 00:10:49

我将其写为:

try {
    return MessageDigest.getInstance("MD5");
} catch (NoSuchAlgorithmException e) {
    throw (AssertionError)new AssertionError("unreachable").initCause(e);
}

并声明因为 catch 块无法访问,所以不需要对其进行测试。

I'd write this as:

try {
    return MessageDigest.getInstance("MD5");
} catch (NoSuchAlgorithmException e) {
    throw (AssertionError)new AssertionError("unreachable").initCause(e);
}

And declare that because the catch block is unreachable, it doesn't need to be tested.

稀香 2024-11-15 00:10:49

老实说,在这种情况下,您不需要覆盖该代码,它是不可访问的样板,以确保您不必担心用户代码中的检查异常(大多数情况下,如果您可以解释为什么 2 的原因,98% 的覆盖率就足够了)错过的百分比)

honestly in this case you don't need to cover that code it's non reachable boilerplate to ensure you don't have to worry about checked exceptions in the user code (most of the time 98% coverage is sufficient if you can explain why the 2 percent got missed)

淡笑忘祈一世凡恋 2024-11-15 00:10:49

为了跟进这个问题,可以使用 PowerMock 来完成。

作为摘录,这是我的工作代码:

@RunWith(PowerMockRunner.class)
@PrepareForTest({MyClass.class, MessageDigest.class})
public class MyClassTest {

    private MyClass myClass = new MyClass();
    @Mock private MessageDigest messageDigestMock;

    @Test
    public void shouldDoMethodCall() throws Exception {
        setupMessageDigest();

        String value = myClass.myMethodCall();

        // I use FestAssert here, you can use any framework you like, but you get
        // the general idea
        Assertions.assertThat(value).isEqualToIgnoringCase("hashed_value");
    }

    public void setupMessageDigest() throws Exception {
        PowerMockito.mockStatic(MessageDigest.class);
        when(MessageDigest.getInstance("SHA1")).thenReturn(messageDigestMock);
        when(messageDigestMock.digest(Matchers.<byte[]>anyObject())).thenReturn("hashed_value".getBytes());
    }

}

“MyClass”类将简单地执行类似以下操作:

public class MyClass {

    public String myMethodCall() {

        return new String(MessageDigest.getInstance("SHA1").digest("someString".getBytes()));

    }        

}

在附加测试中,您可以编写

when(MessageDigest.getInstance("SHA1")).thenThrow(new NoSuchAlgorithmException());

而不是我提到的 return 来到达您的 catch 块。

但请注意,使用 PowerMock 有一些缺点。它通常会使用更多的内存和更多的启动时间,因此您的测试将运行更长时间。对于这个特定的测试来说,这不会产生太大的影响,但只是需要注意一下。

Just to have a follow-up on this question, it can be done with PowerMock.

As an extract, this is my working code:

@RunWith(PowerMockRunner.class)
@PrepareForTest({MyClass.class, MessageDigest.class})
public class MyClassTest {

    private MyClass myClass = new MyClass();
    @Mock private MessageDigest messageDigestMock;

    @Test
    public void shouldDoMethodCall() throws Exception {
        setupMessageDigest();

        String value = myClass.myMethodCall();

        // I use FestAssert here, you can use any framework you like, but you get
        // the general idea
        Assertions.assertThat(value).isEqualToIgnoringCase("hashed_value");
    }

    public void setupMessageDigest() throws Exception {
        PowerMockito.mockStatic(MessageDigest.class);
        when(MessageDigest.getInstance("SHA1")).thenReturn(messageDigestMock);
        when(messageDigestMock.digest(Matchers.<byte[]>anyObject())).thenReturn("hashed_value".getBytes());
    }

}

The class "MyClass" will simply do something like:

public class MyClass {

    public String myMethodCall() {

        return new String(MessageDigest.getInstance("SHA1").digest("someString".getBytes()));

    }        

}

In an additional test, you could write

when(MessageDigest.getInstance("SHA1")).thenThrow(new NoSuchAlgorithmException());

instead of my mentioned return, to get to your catch block.

Do note, however, that using PowerMock has some drawbacks. It will generally use more memory and more instatiation time, so your test will run longer. For this specific test, it won't make a big difference, but just as a head's up.

忆离笙 2024-11-15 00:10:49

您的异常是无法访问的,因为该异常永远不会被抛出。我认为像 Mockito 这样的东西做类似的事情是合乎逻辑的:

doThrow(new NoSuchAlgorithmException()).when(MessageDigest.getInstance("MD5")); // this is psuedo code

但它仍然没有多大意义。您最好像这样编写代码:

private static final MessageDigest MD5_DIGEST;
static {
   try {
      MD5_DIGEST = MessageDigest.getInstance("MD5");
   ///CLOVER:OFF
   } catch (Exception e) {
      // can't happen since MD5 is a known digest
   }
   ///CLOVER:ON
}

public MessageDigest getMessageDigest() {
   return MD5_DIGEST;
}

否则您需要修改您的方法以使其可测试:

public MessageDigest getMessageDigest(String digest) throws NoSuchAlgorithmException {
   return MessageDigest.getInstance(digest);
}

Your exception is unreachable because that exception will never be thrown. I suppose it's logical with something like Mockito to do something akin to:

doThrow(new NoSuchAlgorithmException()).when(MessageDigest.getInstance("MD5")); // this is psuedo code

But it still doesn't make much sense. You are better off writing your code like:

private static final MessageDigest MD5_DIGEST;
static {
   try {
      MD5_DIGEST = MessageDigest.getInstance("MD5");
   ///CLOVER:OFF
   } catch (Exception e) {
      // can't happen since MD5 is a known digest
   }
   ///CLOVER:ON
}

public MessageDigest getMessageDigest() {
   return MD5_DIGEST;
}

otherwise you'll need to modify your method to be testable:

public MessageDigest getMessageDigest(String digest) throws NoSuchAlgorithmException {
   return MessageDigest.getInstance(digest);
}
新一帅帅 2024-11-15 00:10:49

就我而言,我们的管道需要 100% 的覆盖率,因此我执行了以下操作:

  • 定义一个静态内部类,仅使用一个静态方法来返回 MessageDigest 的实例
  • 定义该方法,就像 @TomAnderson 所做的那样: catch 子句,抛出一个 AssertionError("unreachable", e) 来表明肯定不可能到达这里
  • 忽略 jacoco.gradle 用于 jacocoTestReport 和 jacocoTestCoverageVerification 任务。要了解如何排除内部类,请查看我的其他帖子: 使用 Gradle 时如何忽略 Jacoco 中的内部静态类
    (它链接到另一篇关于如何在 Maven 中执行此操作的文章,以防您使用它)

我将该方法提取到一个类中,因为 Gradle 没有一致的语法来忽略类中的成员。检查 Jacoco 的过滤选项此处

In my case, our pipeline needs 100% coverage, so I did the following:

  • define a static inner class with only one static method to return the instance of MessageDigest
  • define the method just as @TomAnderson did: in catch clause, throw an AssertionError("unreachable", e) to indicate that it is definitely impossible to reach here
  • ignore this static class in jacoco.gradle for jacocoTestReport and jacocoTestCoverageVerification tasks. To know how to exclude inner class, check my other post: How to ignore inner static classes in Jacoco when using Gradle
    (which links to another post of how to do it in Maven, in case you use it)

I extract the method to a class, because Gradle does not have a consistent syntax to ignore members in a class. Check Filtering options of Jacoco and here

小ぇ时光︴ 2024-11-15 00:10:49

您可以创建一个包装器 MessageDigest 类:

@Component
public class MessageDigest {
   public java.security.MessageDigest getInstance(String algorithm) throws NoSuchAlgorithmException {
    return java.security.MessageDigest.getInstance(algorithm);
   }
}

You can create a wrapper MessageDigest class:

@Component
public class MessageDigest {
   public java.security.MessageDigest getInstance(String algorithm) throws NoSuchAlgorithmException {
    return java.security.MessageDigest.getInstance(algorithm);
   }
}
咆哮 2024-11-15 00:10:49

我遇到了类似的东西,发现这篇文章对于模拟 MessageDigest.getInstance() 非常有用。

https://www.baeldung.com/mockito-mock-static-methods

@Test
void mockStatic() throws ABCException {
    String acc= "123";
    String pid= "345";
    String pmd= "5056";
    String epi= "1923";

    try (MockedStatic<MessageDigest> messageDigestMockedStatic = Mockito.mockStatic(MessageDigest.class)) {
        messageDigestMockedStatic.when(() -> MessageDigest.getInstance(AppConstants.SHA_256)).thenThrow(NoSuchAlgorithmException.class);
        assertThrows(ABCException.class, () -> absService.createToken(acc, pid, pmd, epi, false));
    }

}

I came across something similar and found this article very useful for mocking MessageDigest.getInstance().

https://www.baeldung.com/mockito-mock-static-methods

@Test
void mockStatic() throws ABCException {
    String acc= "123";
    String pid= "345";
    String pmd= "5056";
    String epi= "1923";

    try (MockedStatic<MessageDigest> messageDigestMockedStatic = Mockito.mockStatic(MessageDigest.class)) {
        messageDigestMockedStatic.when(() -> MessageDigest.getInstance(AppConstants.SHA_256)).thenThrow(NoSuchAlgorithmException.class);
        assertThrows(ABCException.class, () -> absService.createToken(acc, pid, pmd, epi, false));
    }

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