Stub Runner JUnit Rule 和 Stub Runner JUnit5 Extension
Stub Runner 隨附 JUnit 規則,可讓您下載並執行給定群組和 Artifact ID 的 Stub,如下列範例所示
@ClassRule
public static StubRunnerRule rule = new StubRunnerRule().repoRoot(repoRoot())
.stubsMode(StubRunnerProperties.StubsMode.REMOTE)
.downloadStub("org.springframework.cloud.contract.verifier.stubs", "loanIssuance")
.downloadStub("org.springframework.cloud.contract.verifier.stubs:fraudDetectionServer");
@BeforeClass
@AfterClass
public static void setupProps() {
System.clearProperty("stubrunner.repository.root");
System.clearProperty("stubrunner.classifier");
}
StubRunnerExtension
也適用於 JUnit 5。StubRunnerRule
和 StubRunnerExtension
的運作方式非常相似。在呼叫規則或擴充功能後,Stub Runner 會連線到您的 Maven 儲存庫,並針對給定的依賴項清單嘗試進行以下操作:
-
下載它們
-
在本機快取它們
-
將它們解壓縮到暫存資料夾
-
為每個 Maven 依賴項在提供的端口範圍或提供的端口中的隨機端口上啟動 WireMock 伺服器
-
使用所有有效的 WireMock 定義的 JSON 檔案饋送 WireMock 伺服器
-
發送訊息(請記住傳遞
MessageVerifierSender
介面的實作)
Stub Runner 使用 Eclipse Aether 機制來下載 Maven 依賴項。請查看他們的 文件 以獲取更多資訊。
由於 StubRunnerRule
和 StubRunnerExtension
實作了 StubFinder
,它們可讓您找到已啟動的 Stub,如下列範例所示
import java.net.URL;
import java.util.Collection;
import java.util.Map;
import org.springframework.cloud.contract.spec.Contract;
/**
* Contract for finding registered stubs.
*
* @author Marcin Grzejszczak
*/
public interface StubFinder extends StubTrigger {
/**
* For the given groupId and artifactId tries to find the matching URL of the running
* stub.
* @param groupId - might be null. In that case a search only via artifactId takes
* place
* @param artifactId - artifact id of the stub
* @return URL of a running stub or throws exception if not found
* @throws StubNotFoundException in case of not finding a stub
*/
URL findStubUrl(String groupId, String artifactId) throws StubNotFoundException;
/**
* For the given Ivy notation {@code [groupId]:artifactId:[version]:[classifier]}
* tries to find the matching URL of the running stub. You can also pass only
* {@code artifactId}.
* @param ivyNotation - Ivy representation of the Maven artifact
* @return URL of a running stub or throws exception if not found
* @throws StubNotFoundException in case of not finding a stub
*/
URL findStubUrl(String ivyNotation) throws StubNotFoundException;
/**
* @return all running stubs
*/
RunningStubs findAllRunningStubs();
/**
* @return the list of Contracts
*/
Map<StubConfiguration, Collection<Contract>> getContracts();
}
以下範例提供了有關使用 Stub Runner 的更多詳細資訊
@ClassRule
@Shared
StubRunnerRule rule = new StubRunnerRule()
.stubsMode(StubRunnerProperties.StubsMode.REMOTE)
.repoRoot(StubRunnerRuleSpec.getResource("/m2repo/repository").toURI().toString())
.downloadStub("org.springframework.cloud.contract.verifier.stubs", "loanIssuance")
.downloadStub("org.springframework.cloud.contract.verifier.stubs:fraudDetectionServer")
.withMappingsOutputFolder("target/outputmappingsforrule")
def 'should start WireMock servers'() {
expect: 'WireMocks are running'
rule.findStubUrl('org.springframework.cloud.contract.verifier.stubs', 'loanIssuance') != null
rule.findStubUrl('loanIssuance') != null
rule.findStubUrl('loanIssuance') == rule.findStubUrl('org.springframework.cloud.contract.verifier.stubs', 'loanIssuance')
rule.findStubUrl('org.springframework.cloud.contract.verifier.stubs:fraudDetectionServer') != null
and:
rule.findAllRunningStubs().isPresent('loanIssuance')
rule.findAllRunningStubs().isPresent('org.springframework.cloud.contract.verifier.stubs', 'fraudDetectionServer')
rule.findAllRunningStubs().isPresent('org.springframework.cloud.contract.verifier.stubs:fraudDetectionServer')
and: 'Stubs were registered'
"${rule.findStubUrl('loanIssuance').toString()}/name".toURL().text == 'loanIssuance'
"${rule.findStubUrl('fraudDetectionServer').toString()}/name".toURL().text == 'fraudDetectionServer'
}
def 'should output mappings to output folder'() {
when:
def url = rule.findStubUrl('fraudDetectionServer')
then:
new File("target/outputmappingsforrule", "fraudDetectionServer_${url.port}").exists()
}
@Test
public void should_start_wiremock_servers() throws Exception {
// expect: 'WireMocks are running'
then(rule.findStubUrl("org.springframework.cloud.contract.verifier.stubs", "loanIssuance")).isNotNull();
then(rule.findStubUrl("loanIssuance")).isNotNull();
then(rule.findStubUrl("loanIssuance"))
.isEqualTo(rule.findStubUrl("org.springframework.cloud.contract.verifier.stubs", "loanIssuance"));
then(rule.findStubUrl("org.springframework.cloud.contract.verifier.stubs:fraudDetectionServer")).isNotNull();
// and:
then(rule.findAllRunningStubs().isPresent("loanIssuance")).isTrue();
then(rule.findAllRunningStubs().isPresent("org.springframework.cloud.contract.verifier.stubs",
"fraudDetectionServer")).isTrue();
then(rule.findAllRunningStubs()
.isPresent("org.springframework.cloud.contract.verifier.stubs:fraudDetectionServer")).isTrue();
// and: 'Stubs were registered'
then(httpGet(rule.findStubUrl("loanIssuance").toString() + "/name")).isEqualTo("loanIssuance");
then(httpGet(rule.findStubUrl("fraudDetectionServer").toString() + "/name")).isEqualTo("fraudDetectionServer");
}
// Visible for Junit
@RegisterExtension
static StubRunnerExtension stubRunnerExtension = new StubRunnerExtension().repoRoot(repoRoot())
.stubsMode(StubRunnerProperties.StubsMode.REMOTE)
.downloadStub("org.springframework.cloud.contract.verifier.stubs", "loanIssuance")
.downloadStub("org.springframework.cloud.contract.verifier.stubs:fraudDetectionServer")
.withMappingsOutputFolder("target/outputmappingsforrule");
@BeforeAll
@AfterAll
static void setupProps() {
System.clearProperty("stubrunner.repository.root");
System.clearProperty("stubrunner.classifier");
}
private static String repoRoot() {
try {
return StubRunnerRuleJUnitTest.class.getResource("/m2repo/repository/").toURI().toString();
}
catch (Exception e) {
return "";
}
}
請參閱 JUnit 和 Spring 的通用屬性,以獲取有關如何應用 Stub Runner 全域組態的更多資訊。
若要將 JUnit 規則或 JUnit 5 擴充功能與訊息傳遞一起使用,您必須為規則建構器提供 MessageVerifierSender 和 MessageVerifierReceiver 介面的實作(例如,rule.messageVerifierSender(new MyMessageVerifierSender()) )。如果您不這樣做,那麼每當您嘗試發送訊息時,都會拋出例外。 |
Fluent API
當使用 StubRunnerRule
或 StubRunnerExtension
時,您可以新增一個 Stub 以下載,然後傳遞最後下載的 Stub 的端口。以下範例示範如何執行此操作
@ClassRule
public static StubRunnerRule rule = new StubRunnerRule().repoRoot(repoRoot())
.stubsMode(StubRunnerProperties.StubsMode.REMOTE)
.downloadStub("org.springframework.cloud.contract.verifier.stubs", "loanIssuance").withPort(35465)
.downloadStub("org.springframework.cloud.contract.verifier.stubs:fraudDetectionServer:35466");
@BeforeClass
@AfterClass
public static void setupProps() {
System.clearProperty("stubrunner.repository.root");
System.clearProperty("stubrunner.classifier");
}
對於前面的範例,以下測試是有效的
then(rule.findStubUrl("loanIssuance")).isEqualTo(URI.create("http://localhost:35465").toURL());
then(rule.findStubUrl("fraudDetectionServer")).isEqualTo(URI.create("http://localhost:35466").toURL());
搭配 Spring 的 Stub Runner
搭配 Spring 的 Stub Runner 會設定 Stub Runner 專案的 Spring 組態。
透過在您的組態檔中提供 Stub 清單,Stub Runner 會自動下載並在 WireMock 中註冊選定的 Stub。
如果您想找到 Stubbed 依賴項的 URL,您可以自動注入 StubFinder
介面並使用其方法,如下所示
@SpringBootTest(classes = Config, properties = [" stubrunner.cloud.enabled=false",
'foo=${stubrunner.runningstubs.fraudDetectionServer.port}',
'fooWithGroup=${stubrunner.runningstubs.org.springframework.cloud.contract.verifier.stubs.fraudDetectionServer.port}'])
@AutoConfigureStubRunner(mappingsOutputFolder = "target/outputmappings/",
httpServerStubConfigurer = HttpsForFraudDetection)
@ActiveProfiles("test")
class StubRunnerConfigurationSpec {
@Autowired
StubFinder stubFinder
@Autowired
Environment environment
@StubRunnerPort("fraudDetectionServer")
int fraudDetectionServerPort
@StubRunnerPort("org.springframework.cloud.contract.verifier.stubs:fraudDetectionServer")
int fraudDetectionServerPortWithGroupId
@Value('${foo}')
Integer foo
@BeforeAll
static void setupSpec() {
System.clearProperty("stubrunner.repository.root")
System.clearProperty("stubrunner.classifier")
WireMockHttpServerStubAccessor.clear()
}
@AfterAll
static void cleanupSpec() {
setupSpec()
}
@Test
void 'should mark all ports as random'() {
expect:
WireMockHttpServerStubAccessor.everyPortRandom()
}
@Test
void 'should start WireMock servers'() {
expect: 'WireMocks are running'
assert stubFinder.findStubUrl('org.springframework.cloud.contract.verifier.stubs', 'loanIssuance') != null
assert stubFinder.findStubUrl('loanIssuance') != null
assert stubFinder.findStubUrl('loanIssuance') == stubFinder.findStubUrl('org.springframework.cloud.contract.verifier.stubs', 'loanIssuance')
assert stubFinder.findStubUrl('loanIssuance') == stubFinder.findStubUrl('org.springframework.cloud.contract.verifier.stubs:loanIssuance')
assert stubFinder.findStubUrl('org.springframework.cloud.contract.verifier.stubs:loanIssuance:0.0.1-SNAPSHOT') == stubFinder.findStubUrl('org.springframework.cloud.contract.verifier.stubs:loanIssuance:0.0.1-SNAPSHOT:stubs')
assert stubFinder.findStubUrl('org.springframework.cloud.contract.verifier.stubs:fraudDetectionServer') != null
and:
assert stubFinder.findAllRunningStubs().isPresent('loanIssuance')
assert stubFinder.findAllRunningStubs().isPresent('org.springframework.cloud.contract.verifier.stubs', 'fraudDetectionServer')
assert stubFinder.findAllRunningStubs().isPresent('org.springframework.cloud.contract.verifier.stubs:fraudDetectionServer')
and: 'Stubs were registered'
assert "${stubFinder.findStubUrl('loanIssuance').toString()}/name".toURL().text == 'loanIssuance'
assert "${stubFinder.findStubUrl('fraudDetectionServer').toString()}/name".toURL().text == 'fraudDetectionServer'
and: 'Fraud Detection is an HTTPS endpoint'
assert stubFinder.findStubUrl('fraudDetectionServer').toString().startsWith("https")
}
@Test
void 'should throw an exception when stub is not found'() {
when:
BDDAssertions.thenThrownBy(() -> stubFinder.findStubUrl('nonExistingService')).isInstanceOf(StubNotFoundException)
when:
BDDAssertions.thenThrownBy(() -> stubFinder.findStubUrl('nonExistingGroupId', 'nonExistingArtifactId'))
.isInstanceOf(StubNotFoundException)
}
@Test
void 'should register started servers as environment variables'() {
expect:
assert environment.getProperty("stubrunner.runningstubs.loanIssuance.port") != null
assert stubFinder.findAllRunningStubs().getPort("loanIssuance") == (environment.getProperty("stubrunner.runningstubs.loanIssuance.port") as Integer)
and:
assert environment.getProperty("stubrunner.runningstubs.fraudDetectionServer.port") != null
assert stubFinder.findAllRunningStubs().getPort("fraudDetectionServer") == (environment.getProperty("stubrunner.runningstubs.fraudDetectionServer.port") as Integer)
and:
assert environment.getProperty("stubrunner.runningstubs.fraudDetectionServer.port") != null
assert stubFinder.findAllRunningStubs().getPort("fraudDetectionServer") == (environment.getProperty("stubrunner.runningstubs.org.springframework.cloud.contract.verifier.stubs.fraudDetectionServer.port") as Integer)
}
@Test
void 'should be able to interpolate a running stub in the passed test property'() {
given:
int fraudPort = stubFinder.findAllRunningStubs().getPort("fraudDetectionServer")
expect:
assert fraudPort > 0
assert environment.getProperty("foo", Integer) == fraudPort
assert environment.getProperty("fooWithGroup", Integer) == fraudPort
assert foo == fraudPort
}
// @Issue("#573")
@Test
void 'should be able to retrieve the port of a running stub via an annotation'() {
given:
int fraudPort = stubFinder.findAllRunningStubs().getPort("fraudDetectionServer")
expect:
assert fraudPort > 0
assert fraudDetectionServerPort == fraudPort
assert fraudDetectionServerPortWithGroupId == fraudPort
}
@Test
void 'should dump all mappings to a file'() {
when:
def url = stubFinder.findStubUrl("fraudDetectionServer")
then:
assert new File("target/outputmappings/", "fraudDetectionServer_${url.port}").exists()
}
@Configuration
@EnableAutoConfiguration
static class Config {}
@CompileStatic
static class HttpsForFraudDetection extends WireMockHttpServerStubConfigurer {
private static final Log log = LogFactory.getLog(HttpsForFraudDetection)
@Override
WireMockConfiguration configure(WireMockConfiguration httpStubConfiguration, HttpServerStubConfiguration httpServerStubConfiguration) {
if (httpServerStubConfiguration.stubConfiguration.artifactId == "fraudDetectionServer") {
int httpsPort = TestSocketUtils.findAvailableTcpPort()
log.info("Will set HTTPs port [" + httpsPort + "] for fraud detection server")
return httpStubConfiguration
.httpsPort(httpsPort)
}
return httpStubConfiguration
}
}
}
這樣做取決於以下組態檔
stubrunner:
repositoryRoot: classpath:m2repo/repository/
ids:
- org.springframework.cloud.contract.verifier.stubs:loanIssuance
- org.springframework.cloud.contract.verifier.stubs:fraudDetectionServer
- org.springframework.cloud.contract.verifier.stubs:bootService
stubs-mode: remote
除了使用屬性之外,您還可以在 @AutoConfigureStubRunner
內使用屬性。以下範例透過在註解上設定值來達到相同的結果
@AutoConfigureStubRunner(ids = ["org.springframework.cloud.contract.verifier.stubs:loanIssuance",
"org.springframework.cloud.contract.verifier.stubs:fraudDetectionServer",
"org.springframework.cloud.contract.verifier.stubs:bootService"] ,
stubsMode = StubRunnerProperties.StubsMode.REMOTE ,
repositoryRoot = "classpath:m2repo/repository/" )
Stub Runner Spring 會以以下方式為每個註冊的 WireMock 伺服器註冊環境變數。以下範例顯示了 com.example:thing1
和 com.example:thing2
的 Stub Runner ID
-
stubrunner.runningstubs.thing1.port
-
stubrunner.runningstubs.com.example.thing1.port
-
stubrunner.runningstubs.thing2.port
-
stubrunner.runningstubs.com.example.thing2.port
您可以在程式碼中參考這些值。
您也可以使用 @StubRunnerPort
註解來注入正在執行的 Stub 的端口。註解的值可以是 groupid:artifactid
或僅 artifactid
。以下範例示範了 com.example:thing1
和 com.example:thing2
的 Stub Runner ID。
@StubRunnerPort("thing1")
int thing1Port;
@StubRunnerPort("com.example:thing2")
int thing2Port;