You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何链式调用两个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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.13 11:50:54