模拟BouncyCastle类-SecurityException

时间:2018-07-18 08:32:04

标签: java mocking bouncycastle spock

我正在尝试为使用BouncyCastle的SignerInformation的类编写单元测试-我想模拟它的一个实例,但尝试这样做会导致java.lang.SecurityException。这是一个简化的工作示例:

SignerInformationConsumer.java

import org.bouncycastle.cms.SignerInformation;

public class SignerInformationConsumer {
    public String interact(SignerInformation si) {
        return si.getDigestAlgOID();
    }
}

SignerInformationConsumerTest.groovy

import org.bouncycastle.cms.SignerInformation
import spock.lang.Shared
import spock.lang.Specification

class SignerInformationConsumerTest extends Specification {

    @Shared
    SignerInformation si = Mock()

    def "should return valid array"() {
        given:
            SignerInformationConsumer test = new SignerInformationConsumer()
            si.digestAlgOID >> "aaa"
        when:
            String digest = test.interact(si)
        then:
            digest == "aaa"
    }
}

build.gradle

plugins {
    id 'java'
}

group 'test'
version '1.0-SNAPSHOT'

sourceCompatibility = 1.8

repositories {
    mavenCentral()
}

dependencies {
    testCompile group: 'org.spockframework', name: 'spock-core', version: '1.1-groovy-2.4'
    testCompile 'net.bytebuddy:byte-buddy:1.8.0'
    compile group: 'org.bouncycastle', name: 'bcpkix-jdk15on', version: '1.60'
}

例外

java.lang.IllegalArgumentException: Could not create type

    at net.bytebuddy.TypeCache.findOrInsert(TypeCache.java:140)
    at net.bytebuddy.TypeCache$WithInlineExpunction.findOrInsert(TypeCache.java:346)
    at net.bytebuddy.TypeCache.findOrInsert(TypeCache.java:161)
    at net.bytebuddy.TypeCache$WithInlineExpunction.findOrInsert(TypeCache.java:355)
    at org.spockframework.mock.runtime.ProxyBasedMockFactory$ByteBuddyMockFactory.createMock(ProxyBasedMockFactory.java:108)
    at org.spockframework.mock.runtime.ProxyBasedMockFactory.create(ProxyBasedMockFactory.java:65)
    at org.spockframework.mock.runtime.JavaMockFactory.createInternal(JavaMockFactory.java:59)
    at org.spockframework.mock.runtime.JavaMockFactory.create(JavaMockFactory.java:40)
    at org.spockframework.mock.runtime.CompositeMockFactory.create(CompositeMockFactory.java:44)
    at org.spockframework.lang.SpecInternals.createMock(SpecInternals.java:51)
    at org.spockframework.lang.SpecInternals.createMockImpl(SpecInternals.java:296)
    at org.spockframework.lang.SpecInternals.createMockImpl(SpecInternals.java:286)
    at org.spockframework.lang.SpecInternals.MockImpl(SpecInternals.java:89)
    at TestTest.$spock_initializeSharedFields(TestTest.groovy:8)
Caused by: java.lang.IllegalStateException: Error invoking java.lang.ClassLoader#defineClass
    at net.bytebuddy.dynamic.loading.ClassInjector$UsingReflection$Dispatcher$Direct.defineClass(ClassInjector.java:412)
    at net.bytebuddy.dynamic.loading.ClassInjector$UsingReflection.inject(ClassInjector.java:185)
    at net.bytebuddy.dynamic.loading.ClassLoadingStrategy$Default$InjectionDispatcher.load(ClassLoadingStrategy.java:187)
    at net.bytebuddy.dynamic.loading.ClassLoadingStrategy$Default.load(ClassLoadingStrategy.java:120)
    at net.bytebuddy.dynamic.TypeResolutionStrategy$Passive.initialize(TypeResolutionStrategy.java:79)
    at net.bytebuddy.dynamic.DynamicType$Default$Unloaded.load(DynamicType.java:4457)
    at net.bytebuddy.dynamic.DynamicType$Default$Unloaded.load(DynamicType.java:4447)
    at org.spockframework.mock.runtime.ProxyBasedMockFactory$ByteBuddyMockFactory$1.call(ProxyBasedMockFactory.java:113)
    at org.spockframework.mock.runtime.ProxyBasedMockFactory$ByteBuddyMockFactory$1.call(ProxyBasedMockFactory.java:110)
    at net.bytebuddy.TypeCache.findOrInsert(TypeCache.java:138)
    ... 13 more
Caused by: java.lang.SecurityException: class "org.bouncycastle.cms.SignerInformation$SpockMock$bSXMi60o"'s signer information does not match signer information of other classes in the same package
    at java.lang.ClassLoader.checkCerts(ClassLoader.java:898)
    at java.lang.ClassLoader.preDefineClass(ClassLoader.java:668)
    at java.lang.ClassLoader.defineClass(ClassLoader.java:761)
    at net.bytebuddy.dynamic.loading.ClassInjector$UsingReflection$Dispatcher$Direct.defineClass(ClassInjector.java:408)
    ... 22 more

您能提出一些关于如何模拟此类或以不同方式测试行为的解决方案吗?

1 个答案:

答案 0 :(得分:0)

您正在这里处理已签名的JAR。假设您不想全局禁用Java的安全性功能,也不想创建所有BouncyCastle JAR的副本,并删除它们的清单,那么我将向您显示一种解决方法。

问题是Spock模拟(也包括其他模拟)是在与原始类相同的程序包中创建的。但是模拟是未签名的代码,因此是错误消息。现在,您可以将要模拟的类子类化,然后模拟该子类。如果您需要在测试中的任何地方,只需确保子类具有其父级所有必需的构造函数即可。

package de.scrum_master.stackoverflow;

import org.bouncycastle.cms.SignerInformation;

public class SignerInformationConsumer {
  public String interact(SignerInformation si) {
    return si.getDigestAlgOID();
  }
}
package de.scrum_master.stackoverflow

import org.bouncycastle.cms.SignerInformation
import spock.lang.Specification

class SignerInformationConsumerTest extends Specification {

  static class SignerInformationMock extends SignerInformation {
    protected SignerInformationMock(SignerInformation baseInfo) {
      super(baseInfo)
    }
  }

  //SignerInformation signerInformation = Spy(SignerInformationMock, useObjenesis: true)
  SignerInformation signerInformation = Mock(SignerInformationMock)

  def "should return valid array"() {
    given:
    SignerInformationConsumer signerInformationConsumer = new SignerInformationConsumer()
    signerInformation.getDigestAlgOID() >> "aaa"
    expect:
    signerInformationConsumer.interact(signerInformation) == "aaa"
  }
}

关于您的测试的几句话:

  • 请勿使用@Shared,因为测试应该相互独立。您可以将特征方法A的副作用放到B中。共享变量仅应在非常罕见的情况下使用,例如如果您创建的对象在时间或资源上非常昂贵。模拟绝对不是。因此,要么在您的功能方法内部创建模拟,要么,如果其他功能方法要使用相同的模拟定义,请使用不带@Shared的普通成员变量。您当然可以忽略此建议,但我仍然认为您应该遵循该建议。

  • 您的测试不会测试应用程序,它只会测试模拟程序。我希望您的真实测试用例看起来有所不同,因为即使我为您工作,该测试也只能检查存根结果是否符合测试开始时指定的内容。

  • 我的代码中带注释的行向您展示了如果出于任何原因(例如,您想使用真实的对象并仅存一个或几种方法),如何使用间谍而不是模拟。在这种特殊情况下,您将需要Objenesis作为依赖项,否则,因为没有默认构造函数,您将获得异常。另外,您必须创建间谍并包含构造函数参数。

  • 如果要在每个功能方法中对getDigestAlgOID()进行相同的存根,则可以将存根部分从Feature方法移到模拟定义中,如下所示:

SignerInformation signerInformation = Mock(SignerInformationMock) {
  getDigestAlgOID() >> "aaa"
}