如何链式调用两个Spring Cloud Contract并传递响应中的cookie字段
解决Spring Cloud Contract跨契约传递响应值问题
问题描述
编写两个Spring Cloud Contract时,要求第二个契约(contract2)请求体的cookie字段值等于第一个契约(contract1)响应中的cookie字段值,但尝试提取该值时出现编译错误或NullPointerException:
- 最初尝试
String cookie = getBody().properties.get('data').properties.get('cookie')触发编译错误 - 后续在contract1的response块后添加
String cookie = getResponse().properties.get("data").properties.get("cookie"),执行mvn clean spring-cloud-contract:generateTests时抛出NullPointerException
原契约代码:
package contracts import org.springframework.cloud.contract.spec.Contract def contract1 = Contract.make { request { method 'POST' url '/contract1' headers { contentType('application/json') } body(["url" : "test", "name": "test", "dnsServer": "8.8.8.8", "vlan" : "52"]) } response { status 200 body("data" : ["cookie": anyNonBlankString()]]) headers { contentType(applicationJson()) } } } def contract2 = Contract.make { request { method 'POST' url '/contract2' headers { contentType('application/json') } body(["model" : "test model", "cookie" : <shall be the cookie generated by the response of contract1> ]) } response { status 200 } } [contract1, contract2]
执行时的错误信息:
[ERROR] Exception occurred while trying to evaluate the contract at path [C:\neilb\pagination\workspace\src\test\resources\contracts\ve_lifecycle.groovy] java.lang.NullPointerException: Cannot get property 'properties' on null object at org.codehaus.groovy.runtime.NullObject.getProperty (NullObject.java:60) at org.codehaus.groovy.runtime.InvokerHelper.getProperty (InvokerHelper.java:190) at org.codehaus.groovy.runtime.callsite.NullCallSite.getProperty (NullCallSite.java:46) at org.codehaus.groovy.runtime.callsite.AbstractCallSite.callGetProperty (AbstractCallSite.java:298) at contracts.ve_lifecycle$_run_closure1.doCall (ve_lifecycle.groovy:31) at contracts.ve_lifecycle$_run_closure1.doCall (ve_lifecycle.groovy) at jdk.internal.reflect.NativeMethodAccessorImpl.invoke0 (Native Method) at jdk.internal.reflect.NativeMethodAccessorImpl.invoke (NativeMethodAccessorImpl.java:62) at jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke (DelegatingMethodAccessorImpl.java:43) at java.lang.reflect.Method.invoke (Method.java:567) at org.codehaus.groovy.reflection.CachedMethod.invoke (CachedMethod.java:101) at groovy.lang.MetaMethod.doMethodInvoke (MetaMethod.java:323) at org.codehaus.groovy.runtime.metaclass.ClosureMetaClass.invokeMethod (ClosureMetaClass.java:263) at groovy.lang.MetaClassImpl.invokeMethod (MetaClassImpl.java:1041) at groovy.lang.Closure.call (Closure.java:405) at groovy.lang.Closure.call (Closure.java:399) at org.springframework.cloud.contract.spec.Contract.make (Contract.java:180) at org.springframework.cloud.contract.spec.Contract$make.call (Unknown Source) at org.codehaus.groovy.runtime.callsite.CallSiteArray.defaultCall (CallSiteArray.java:47) at org.codehaus.groovy.runtime.callsite.AbstractCallSite.call (AbstractCallSite.java:115) at org.codehaus.groovy.runtime.callsite.AbstractCallSite.call (AbstractCallSite.java:127) at contracts.ve_lifecycle.run (ve_lifecycle.groovy:5) at groovy.lang.GroovyShell.evaluate (GroovyShell.java:443) at groovy.lang.GroovyShell.evaluate (GroovyShell.java:490) at org.springframework.cloud.contract.verifier.util.ContractVerifierDslConverter.toObject (ContractVerifierDslConverter.groovy:141) at org.springframework.cloud.contract.verifier.util.ContractVerifierDslConverter.convertAsCollection (ContractVerifierDslConverter.groovy:99) at org.springframework.cloud.contract.verifier.file.ContractFileScanner.appendRecursively (ContractFileScanner.groovy:249) at org.springframework.cloud.contract.verifier.file.ContractFileScanner.findContractsRecursively (ContractFileScanner.groovy:221) at org.springframework.cloud.contract.verifier.TestGenerator.generateTestClasses (TestGenerator.groovy:125) at org.springframework.cloud.contract.verifier.TestGenerator.generate (TestGenerator.groovy:104) at org.springframework.cloud.contract.maven.verifier.GenerateTestsMojo.execute (GenerateTestsMojo.java:301) at org.apache.maven.plugin.DefaultBuildPluginManager.executeMojo (DefaultBuildPluginManager.java:137) at org.apache.maven.lifecycle.internal.MojoExecutor.execute (MojoExecutor.java:210) at org.apache.maven.lifecycle.internal.MojoExecutor.execute (MojoExecutor.java:156) at org.apache.maven.lifecycle.internal.MojoExecutor.execute (MojoExecutor.java:148) at org.apache.maven.lifecycle.internal.LifecycleModuleBuilder.buildProject (LifecycleModuleBuilder.java:117) at org.apache.maven.lifecycle.internal.LifecycleModuleBuilder.buildProject (LifecycleModuleBuilder.java:81) at org.apache.maven.lifecycle.internal.builder.singlethreaded.SingleThreadedBuilder.build (SingleThreadedBuilder.java:56) at org.apache.maven.lifecycle.internal.LifecycleStarter.execute (LifecycleStarter.java:128) at org.apache.maven.DefaultMaven.doExecute (DefaultMaven.java:305) at org.apache.maven.DefaultMaven.doExecute (DefaultMaven.java:192) at org.apache.maven.DefaultMaven.execute (DefaultMaven.java:105) at org.apache.maven.cli.MavenCli.execute (MavenCli.java:957) at org.apache.maven.cli.MavenCli.doMain (MavenCli.java:289) at org.apache.maven.cli.MavenCli.main (MavenCli.java:193) at jdk.internal.reflect.NativeMethodAccessorImpl.invoke0 (Native Method) at jdk.internal.reflect.NativeMethodAccessorImpl.invoke (NativeMethodAccessorImpl.java:62) at jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke (DelegatingMethodAccessorImpl.java:43) at java.lang.reflect.Method.invoke (Method.java:567) at org.codehaus.plexus.classworlds.launcher.Launcher.launchEnhanced (Launcher.java:282) at org.codehaus.plexus.classworlds.launcher.Launcher.launch (Launcher.java:225) at org.codehaus.plexus.classworlds.launcher.Launcher.mainWithExitCode (Launcher.java:406) at org.codehaus.plexus.classworlds.launcher.Launcher.main (Launcher.java:347)
原因分析
直接在Contract闭包外部调用getResponse()会返回null,因为Spring Cloud Contract的DSL上下文限制,getResponse()仅能在单个Contract的闭包内部特定场景使用,跨契约共享数据必须使用官方提供的变量存储与引用机制,通过value()方法关联消费者和生产者端的逻辑。
修复方案
定义一个共享变量,在contract1的响应中绑定该变量,在contract2的请求中直接引用:
package contracts import org.springframework.cloud.contract.spec.Contract import java.util.UUID // 定义共享cookie变量:消费者端匹配任意非空字符串,生产者端生成动态UUID(也可指定固定值) def sharedCookie = value( consumer(regex(nonBlankString())), producer(UUID.randomUUID().toString()) ) def contract1 = Contract.make { request { method 'POST' url '/contract1' headers { contentType('application/json') } body([ "url" : "test", "name" : "test", "dnsServer" : "8.8.8.8", "vlan" : "52" ]) } response { status 200 body([ "data": [ "cookie": sharedCookie ] ]) headers { contentType(applicationJson()) } } } def contract2 = Contract.make { request { method 'POST' url '/contract2' headers { contentType('application/json') } body([ "model" : "test model", "cookie": sharedCookie // 引用共享变量 ]) } response { status 200 } } [contract1, contract2]
代码说明
value(consumer(...), producer(...)):Spring Cloud Contract用于定义双向变量的方法consumer(regex(nonBlankString())):告诉消费者测试逻辑,只要请求的cookie是任意非空字符串就匹配通过producer(UUID.randomUUID().toString()):指定生产者测试时返回的动态cookie值,也可替换为固定字符串(如"fixed-cookie-value")
- 两个契约通过同一个
sharedCookie变量关联,确保contract2请求使用的cookie与contract1响应返回的cookie一致
内容的提问来源于stack exchange,提问作者NeilB
相关产品推荐
相关产品推荐

