使用 Stub Runner Boot 应用程序

警告

由于当前artifact仓库发布工具的限制,我们目前无法发布可执行的jar包,并且从4.1.6版本开始,我们跳过了此artifact的发布。Stub Runner Boot仍然可以通过Docker Stub Runner Boot镜像获取,这是使用该应用程序的首选方式。您还可以访问项目仓库中的源代码并自行构建应用程序。如果artifact仓库工具进行了必要的调整,我们将恢复发布此jar包。

Spring Cloud Contract Stub Runner Boot是一个Spring Boot应用程序,它暴露REST端点以触发消息标签并访问WireMock服务器。

Stub Runner Boot安全性

Stub Runner Boot应用程序在设计上是不安全的——对其进行安全加固需要为所有存根添加安全性,即使它们实际上不需要。由于这是一个测试工具,该服务器不应用于生产环境。

预计只有受信任的客户端才能访问Stub Runner Boot服务器。您不应在不受信任的位置将此应用程序作为Fat Jar或Docker镜像运行。

Stub Runner服务器

要使用Stub Runner服务器,请添加以下依赖

compile "org.springframework.cloud:spring-cloud-starter-stub-runner"

然后用@EnableStubRunnerServer注解一个类,构建一个fat jar,就可以使用了。

有关属性,请参阅Stub Runner Spring部分。

Stub Runner Server Fat Jar

您可以通过运行以下命令从Maven下载一个独立的JAR(例如,版本2.0.1.RELEASE)

$ wget -O stub-runner.jar 'https://search.maven.org/remotecontent?filepath=org/springframework/cloud/spring-cloud-contract-stub-runner-boot/2.0.1.RELEASE/spring-cloud-contract-stub-runner-boot-2.0.1.RELEASE.jar'
$ java -jar stub-runner.jar --spring.cloud.contract.stubrunner.ids=... --spring.cloud.contract.stubrunner.repositoryRoot=...

Spring Cloud CLI

Spring Cloud CLI项目的1.4.0.RELEASE版本开始,您可以通过运行spring cloud stubrunner来启动Stub Runner Boot。

要传递配置,您可以在当前工作目录、名为config的子目录或~/.spring-cloud中创建一个spring.cloud.contract.stubrunner.yml文件。该文件可以类似于以下示例,用于运行本地安装的存根

示例1. stubrunner.yml
spring.cloud.contract.stubrunner:
  stubsMode: LOCAL
  ids:
    - com.example:beer-api-producer:+:9876

然后,您可以在终端窗口中调用spring cloud stubrunner来启动Stub Runner服务器。它在端口8750可用。

端点

Stub Runner Boot提供两个端点

HTTP

对于HTTP,Stub Runner Boot提供以下端点

  • GET /stubs:以ivy:integer表示法返回所有正在运行的存根列表

  • GET /stubs/{ivy}:返回给定ivy表示法的端口(调用端点时,ivy也可以仅是artifactId

消息

对于消息传递,Stub Runner Boot提供以下端点

  • GET /triggers:返回所有正在运行的标签列表,采用ivy : [ label1, label2 …​]表示法

  • POST /triggers/{label}:运行带有label的触发器

  • POST /triggers/{ivy}/{label}:运行带有给定ivy表示法的label的触发器(调用端点时,ivy也可以仅是artifactId

示例

以下示例展示了Stub Runner Boot的典型用法

@SpringBootTest(classes = StubRunnerBoot, properties = "spring.cloud.zookeeper.enabled=false")
@ActiveProfiles("test")
class StubRunnerBootSpec {

	@Autowired
	StubRunning stubRunning

	@BeforeEach
	void setup() {
		RestAssuredMockMvc.standaloneSetup(new HttpStubsController(stubRunning),
				new TriggerController(stubRunning))
	}

	@Test
	void 'should return a list of running stub servers in "full ivy port" notation'() {
		when:
			String response = RestAssuredMockMvc.get('/stubs').body.asString()
		then:
			def root = new JsonSlurper().parseText(response)
			assert root.'org.springframework.cloud.contract.verifier.stubs:bootService:0.0.1-SNAPSHOT:stubs' instanceof Integer
	}

	@Test
	void 'should return a port on which a #stubId stub is running'() {
		given:
		def stubIds = ['org.springframework.cloud.contract.verifier.stubs:bootService:+:stubs',
				   'org.springframework.cloud.contract.verifier.stubs:bootService:0.0.1-SNAPSHOT:stubs',
				   'org.springframework.cloud.contract.verifier.stubs:bootService:+',
				   'org.springframework.cloud.contract.verifier.stubs:bootService',
				   'bootService']
		stubIds.each {
			when:
				def response = RestAssuredMockMvc.get("/stubs/${it}")
			then:
				assert response.statusCode == 200
				assert Integer.valueOf(response.body.asString()) > 0
		}
	}

	@Test
	void 'should return 404 when missing stub was called'() {
		when:
			def response = RestAssuredMockMvc.get("/stubs/a:b:c:d")
		then:
			assert response.statusCode == 404
	}

	@Test
	void 'should return a list of messaging labels that can be triggered when version and classifier are passed'() {
		when:
			String response = RestAssuredMockMvc.get('/triggers').body.asString()
		then:
			def root = new JsonSlurper().parseText(response)
			assert root.'org.springframework.cloud.contract.verifier.stubs:bootService:0.0.1-SNAPSHOT:stubs'?.containsAll(["return_book_1"])
	}

	@Test
	void 'should trigger a messaging label'() {
		given:
			StubRunning stubRunning = Mockito.mock(StubRunning)
			RestAssuredMockMvc.standaloneSetup(new HttpStubsController(stubRunning), new TriggerController(stubRunning))
		when:
			def response = RestAssuredMockMvc.post("/triggers/delete_book")
		then:
			response.statusCode == 200
		and:
			Mockito.verify(stubRunning).trigger('delete_book')
	}

	@Test
	void 'should trigger a messaging label for a stub with #stubId ivy notation'() {
		given:
			StubRunning stubRunning = Mockito.mock(StubRunning)
			RestAssuredMockMvc.standaloneSetup(new HttpStubsController(stubRunning), new TriggerController(stubRunning))
		and:
			def stubIds = ['org.springframework.cloud.contract.verifier.stubs:bootService:stubs', 'org.springframework.cloud.contract.verifier.stubs:bootService', 'bootService']
		stubIds.each {
			when:
				def response = RestAssuredMockMvc.post("/triggers/$it/delete_book")
			then:
				assert response.statusCode == 200
			and:
				Mockito.verify(stubRunning).trigger(it, 'delete_book')
		}

	}

	@Test
	void 'should throw exception when trigger is missing'() {
		when:
		BDDAssertions.thenThrownBy(() -> RestAssuredMockMvc.post("/triggers/missing_label"))
		.hasMessageContaining("Exception occurred while trying to return [missing_label] label.")
		.hasMessageContaining("Available labels are")
		.hasMessageContaining("org.springframework.cloud.contract.verifier.stubs:loanIssuance:0.0.1-SNAPSHOT:stubs=[]")
		.hasMessageContaining("org.springframework.cloud.contract.verifier.stubs:bootService:0.0.1-SNAPSHOT:stubs=")
	}

}

Stub Runner Boot与服务发现

使用Stub Runner Boot的一种方式是将其用作“冒烟测试”的存根源。这意味着什么?假设您不想将50个微服务部署到测试环境以查看您的应用程序是否工作。您已经在构建过程中运行了一套测试,但您还想确保您的应用程序打包工作正常。您可以将您的应用程序部署到环境中,启动它,并对其运行几个测试以查看它是否工作。我们可以将这些测试称为“冒烟测试”,因为它们的目的只是检查少数测试场景。

这种方法的问题是,如果您使用微服务,您很可能也使用服务发现工具。Stub Runner Boot允许您通过启动所需的存根并将它们注册到服务发现工具中来解决这个问题。

现在假设我们想要启动这个应用程序,以便存根能够自动注册。我们可以通过运行java -jar ${SYSTEM_PROPS} stub-runner-boot-eureka-example.jar来做到这一点,其中${SYSTEM_PROPS}

这样,您部署的应用程序就可以通过服务发现向已启动的WireMock服务器发送请求。最有可能的是,第1到3点可以在application.yml中默认设置,因为它们不太可能更改。这样,您只需在每次启动Stub Runner Boot时提供要下载的存根列表即可。

© . This site is unofficial and not affiliated with VMware.