スタブランナー JUnit ルールとスタブランナー JUnit5 拡張機能

スタブランナーには、次の例に示すように、特定のグループとアーティファクト ID のスタブをダウンロードして実行できる JUnit ルールが付属しています。

@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");
}

JUnit 5 では StubRunnerExtension も利用できます。StubRunnerRule と StubRunnerExtension は非常に似た方法で動作します。ルールまたは拡張機能が呼び出された後、Stub Runner は Maven リポジトリに接続し、指定された依存関係のリストに対して次のことを試行します。

  • ダウンロードしてください

  • ローカルにキャッシュする

  • 一時フォルダーに解凍します

  • 指定された範囲のポートまたは指定されたポートからのランダムなポートで、Maven 依存関係ごとに WireMock サーバーを起動します。

  • 有効な WireMock 定義であるすべての JSON ファイルを WireMock サーバーにフィードします

  • メッセージの送信 (MessageVerifierSender インターフェースの実装を渡すことを忘れないでください)

スタブランナーは、Eclipse Aether (英語) メカニズムを使用して Maven 依存関係をダウンロードします。詳細については、ドキュメント (英語) を確認してください。

StubRunnerRule および StubRunnerExtension は StubFinder を実装しているため、次の例に示すように、開始されたスタブを見つけることができます。

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();

}

次の例では、スタブランナーの使用方法について詳しく説明します。

Spock
@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()
}
Junit 4
@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");
}
Junit 5
// 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 の共通プロパティを参照してください。

JUnit ルールまたは JUnit 5 拡張機能をメッセージングとともに使用するには、MessageVerifierSender および MessageVerifierReceiver インターフェースの実装をルールビルダー (たとえば、rule.messageVerifierSender(new MyMessageVerifierSender())) に提供する必要があります。これを行わないと、メッセージを送信しようとするたびに例外がスローされます。

Maven 設定

スタブダウンローダーは、別のローカルリポジトリフォルダーの Maven 設定を尊重します。リポジトリとプロファイルの認証の詳細は現在考慮されていないため、上記のプロパティを使用して指定する必要があります。

固定ポートの提供

固定ポートでスタブを実行することもできます。これは 2 つの異なる方法で行うことができます。1 つはプロパティで渡す方法、もう 1 つは JUnit ルールの流れるような API を使用する方法です。

Fluent API

StubRunnerRule または StubRunnerExtension を使用する場合、ダウンロードするスタブを追加し、最後にダウンロードされたスタブのポートを渡すことができます。次の例は、その方法を示しています。

@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 付スタブランナー

Spring を使用したスタブランナーは、スタブランナープロジェクトの Spring 構成をセットアップします。

構成ファイル内にスタブのリストを指定すると、Stub Runner は選択したスタブを自動的にダウンロードして WireMock に登録します。

スタブ化された依存関係の 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 のスタブランナー ID を示しています。

  • stubrunner.runningstubs.thing1.port

  • stubrunner.runningstubs.com.example.thing1.port

  • stubrunner.runningstubs.thing2.port

  • stubrunner.runningstubs.com.example.thing2.port

コード内でこれらの値を参照できます。

@StubRunnerPort アノテーションを使用して、実行中のスタブのポートを挿入することもできます。アノテーションの値は、groupid:artifactid または artifactid のみにすることができます。次の例は、com.example:thing1 および com.example:thing2 のスタブランナー ID を示しています。

@StubRunnerPort("thing1")
int thing1Port;
@StubRunnerPort("com.example:thing2")
int thing2Port;