单元测试出现【com.sun.crypto.provider.HmacSHA1 cannot be cast to javax.crypto.Mac】原因分析

1,666 阅读2分钟

异常出现前提场景:

  • 1.使用Spock测试框架进行对方法进行单元测试
  • 2.测试的目标方法在做数据的3DES解密操作

异常现象

抛出已下异常信息:
java.lang.ClassCastException: com.sun.crypto.provider.DESedeCipher cannot be cast to javax.crypto.CipherSpi
at javax.crypto.Cipher.chooseProvider(Cipher.java:859)
	at javax.crypto.Cipher.init(Cipher.java:1395)
	at javax.crypto.Cipher.init(Cipher.java:1326)
	at com.fingard.gardpay.common.util.TripleDesHelper.decrypt(TripleDesHelper.java:96)
     	......

解决方法

在测试类的头部增加@PowerMockIgnore("javax.crypto.*") 注解即可解决问题,如下所示

@PowerMockIgnore("javax.crypto.*")
class FingardTransResultReturnOutServiceImplTest extends Specification{
	...
}

原因分析

  • 第一步:(分析抛出的异常类型)

    异常类型是【java.lang.ClassCastException】,出现的情况可能为:
    1.进行强制类型转换
    2.两个类的ClassLoader不同,导致JVM认为这两个类没有关联
    3.。。。
    
  • 第二步:(分析为什么测试方法会出现类型转化失败)
    情况一:因为PowerMock的工作原理即是使用自定义的类加载器来加载被修改过的类,从而达到打桩的目的,也正是因为这个机制导致抛出这个异常。该情况网上查阅得到的相关例子原话如下:

    The reason is that the XML framework tries to instantiate classes using reflection and does this from the thread context
    classloader (PowerMock's classloader) but then tries to assign the created object to a field not loaded by the same classloader.
    When this happens you need to make use of the @PowerMockIgnore annotation to tell PowerMock to defer the loading of a certain package to the system classloader. 
    What you need to ignore is case specific but usually it's the XML framework or some packages that interact with it. E.g. @PowerMockIgnore({"org.xml.*", "javax.xml.*"}).
    

情况二:Java的默认策略设置无法使用强度更高的密钥处理加密/解密,为了增加支持,我们需要下载Java™密码学扩展(JCE)无限强度管辖权策略文件。javax.crypto.Cipher类提供加密和解密功能,该类是JCE框架的核心。但是Powermockito无法增强javax.crypto类,因此可以在类级别添加以下注释@PowerMockIgnore("javax.crypto.*")来进行解决.(注意根据参考链接该情况下出现的错误信息是:java.lang.ClassCastException: com.sun.crypto.provider.HmacSHA1 cannot be cast to javax.crypto.MacSpi)

扩展

1.PowerMockito简介
PowerMock是一个扩展了其它如EasyMock等mock框架的、功能更加强大的框架。PowerMock使用一个自定义类加载器和字节码操作来模拟静态方法,构造函数,final类和方法,私有方法,去除静态初始化器等等

2.注解说明
· @RunWith(PowerMockRunner.class)@PrepareForTest([FingardTransResultReturnOutServiceImpl.class])注解:
如果你的测试用例里没有使用注解@PrepareForTest,那么可以不用加注解@RunWith(PowerMockRunner.class),反之亦然。 当你需要使用PowerMock强大功能(Mock静态、final、私有方法等)的时候,就需要加注解@PrepareForTest。这一点和Mockito的使用方式是类似的,要么使用这种注解的方式

· @PowerMockIgnore注解:声明package路径,表示不使用PowerMockito来加载所声明的package路径的类

参考链接

原因分析情况一参考:https://www.cnblogs.com/pangblog/p/3255877.html
原因分析情况二参考:https://stackoverflow.com/questions/7442875/generating-hmacsha256-signature-in-junit/7449301
PowerMockito框架入门参考:https://www.jianshu.com/p/6631bd826677

转载请备明出处,LOVE & PEACE ! --- By HAN