模拟 BouncyCastle 类 - SecurityException
Mocking BouncyCastle classes - SecurityException
我正在尝试为使用 BouncyCastle 的 SignerInformation
的 class 编写单元测试 - 我想模拟它的一个实例,但尝试这样做会导致 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.call(ProxyBasedMockFactory.java:113)
at org.spockframework.mock.runtime.ProxyBasedMockFactory$ByteBuddyMockFactory.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
您能否就如何模拟此 class 或以不同方式测试行为提出一些解决方案?
您正在处理此处签名的 JAR。假设您不想全局禁用 Java 的安全功能,也不想创建所有 BouncyCastle JAR 的副本,删除它们的清单,我将向您展示一个解决方法。
问题在于 Spock 模拟(也包括其他模拟)是在与原始 class 相同的包中创建的。但是模拟是未签名的代码,因此是错误消息。现在你可以只 subclass 要模拟的 class 和模拟 subclass 代替。如果您在测试中的任何地方需要它们,只需确保 subclass 具有来自其父级的所有必要构造函数。
package de.scrum_master.Whosebug;
import org.bouncycastle.cms.SignerInformation;
public class SignerInformationConsumer {
public String interact(SignerInformation si) {
return si.getDigestAlgOID();
}
}
package de.scrum_master.Whosebug
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()
要在每个特征方法中进行相同的存根,您可以将存根部分从特征方法移动到模拟定义中,如下所示:
SignerInformation signerInformation = Mock(SignerInformationMock) {
getDigestAlgOID() >> "aaa"
}
我正在尝试为使用 BouncyCastle 的 SignerInformation
的 class 编写单元测试 - 我想模拟它的一个实例,但尝试这样做会导致 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.call(ProxyBasedMockFactory.java:113)
at org.spockframework.mock.runtime.ProxyBasedMockFactory$ByteBuddyMockFactory.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
您能否就如何模拟此 class 或以不同方式测试行为提出一些解决方案?
您正在处理此处签名的 JAR。假设您不想全局禁用 Java 的安全功能,也不想创建所有 BouncyCastle JAR 的副本,删除它们的清单,我将向您展示一个解决方法。
问题在于 Spock 模拟(也包括其他模拟)是在与原始 class 相同的包中创建的。但是模拟是未签名的代码,因此是错误消息。现在你可以只 subclass 要模拟的 class 和模拟 subclass 代替。如果您在测试中的任何地方需要它们,只需确保 subclass 具有来自其父级的所有必要构造函数。
package de.scrum_master.Whosebug;
import org.bouncycastle.cms.SignerInformation;
public class SignerInformationConsumer {
public String interact(SignerInformation si) {
return si.getDigestAlgOID();
}
}
package de.scrum_master.Whosebug
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()
要在每个特征方法中进行相同的存根,您可以将存根部分从特征方法移动到模拟定义中,如下所示:
SignerInformation signerInformation = Mock(SignerInformationMock) {
getDigestAlgOID() >> "aaa"
}