对于最新的稳定版本,请使用 spring-cloud-contract 4.2.1

使用可插拔架构

您可能会遇到您的合约以其他格式定义的情况, 例如 YAML、RAML 或 PACT。在这些情况下,您仍然希望从自动 生成测试和存根。您可以添加自己的实现来生成两者 测试和存根。此外,您还可以自定义测试的生成方式(例如,您 可以为其他语言生成测试)和存根的生成方式(例如,您 可以为其他 HTTP 服务器实现生成存根)。

自定义合约转换器

ContractConverterinterface 允许您注册自己的合约实现 结构转换器。以下代码清单显示了ContractConverter接口:

import java.io.File;
import java.util.Collection;

/**
 * Converter to be used to convert FROM {@link File} TO {@link Contract} and from
 * {@link Contract} to {@code T}.
 *
 * @param <T> - type to which we want to convert the contract
 * @author Marcin Grzejszczak
 * @since 1.1.0
 */
public interface ContractConverter<T> extends ContractStorer<T>, ContractReader<T> {

	/**
	 * Should this file be accepted by the converter. Can use the file extension to check
	 * if the conversion is possible.
	 * @param file - file to be considered for conversion
	 * @return - {@code true} if the given implementation can convert the file
	 */
	boolean isAccepted(File file);

	/**
	 * Converts the given {@link File} to its {@link Contract} representation.
	 * @param file - file to convert
	 * @return - {@link Contract} representation of the file
	 */
	Collection<Contract> convertFrom(File file);

	/**
	 * Converts the given {@link Contract} to a {@link T} representation.
	 * @param contract - the parsed contract
	 * @return - {@link T} the type to which we do the conversion
	 */
	T convertTo(Collection<Contract> contract);

}

您的实现必须定义它应启动 转换。此外,您必须定义如何在两个方向上执行该转换。

创建实施后,您必须创建一个/META-INF/spring.factories文件中提供 实现。

以下示例显示了一个典型的spring.factories文件:

org.springframework.cloud.contract.spec.ContractConverter=\
org.springframework.cloud.contract.verifier.converter.YamlContractConverter

使用自定义测试生成器

如果您想为 Java 以外的语言生成测试,或者您对 验证程序构建 Java 测试的方式,您可以注册自己的实现。

SingleTestGeneratorinterface 允许您注册自己的实现。这 以下代码清单显示了SingleTestGenerator接口:

import java.nio.file.Path;
import java.util.Collection;

import org.springframework.cloud.contract.verifier.config.ContractVerifierConfigProperties;
import org.springframework.cloud.contract.verifier.file.ContractMetadata;

/**
 * Builds a single test.
 *
 * @since 1.1.0
 */
public interface SingleTestGenerator {

	/**
	 * Creates contents of a single test class in which all test scenarios from the
	 * contract metadata should be placed.
	 * @param properties - properties passed to the plugin
	 * @param listOfFiles - list of parsed contracts with additional metadata
	 * @param generatedClassData - information about the generated class
	 * @param includedDirectoryRelativePath - relative path to the included directory
	 * @return contents of a single test class
	 */
	String buildClass(ContractVerifierConfigProperties properties, Collection<ContractMetadata> listOfFiles,
			String includedDirectoryRelativePath, GeneratedClassData generatedClassData);

	class GeneratedClassData {

		public final String className;

		public final String classPackage;

		public final Path testClassPath;

		public GeneratedClassData(String className, String classPackage, Path testClassPath) {
			this.className = className;
			this.classPackage = classPackage;
			this.testClassPath = testClassPath;
		}

	}

}

同样,您必须提供spring.factories文件,如下所示 例:

org.springframework.cloud.contract.verifier.builder.SingleTestGenerator=/
com.example.MyGenerator

使用自定义存根生成器

如果你想为 WireMock 以外的存根服务器生成存根,你可以将你的 自己的StubGenerator接口。以下代码清单显示了StubGenerator接口:

import java.io.File;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

import org.springframework.cloud.contract.spec.Contract;
import org.springframework.cloud.contract.verifier.file.ContractMetadata;

/**
 * Converts contracts into their stub representation.
 *
 * @param <T> - type of stub mapping
 * @since 1.1.0
 */
public interface StubGenerator<T> {

	/**
	 * @param mapping - potential stub mapping mapping
	 * @return {@code true} if this converter could have generated this mapping stub.
	 */
	default boolean canReadStubMapping(File mapping) {
		return mapping.getName().endsWith(fileExtension());
	}

	/**
	 * @param rootName - root name of the contract
	 * @param content - metadata of the contract
	 * @return the collection of converted contracts into stubs. One contract can result
	 * in multiple stubs.
	 */
	Map<Contract, String> convertContents(String rootName, ContractMetadata content);

	/**
	 * Post process a generated stub mapping.
	 * @param stubMapping - mapping of a stub
	 * @param contract - contract for which stub was generated
	 * @return the converted stub mapping
	 */
	default T postProcessStubMapping(T stubMapping, Contract contract) {
		List<StubPostProcessor> processors = StubPostProcessor.PROCESSORS.stream()
			.filter(p -> p.isApplicable(contract))
			.collect(Collectors.toList());
		if (processors.isEmpty()) {
			return defaultStubMappingPostProcessing(stubMapping, contract);
		}
		T stub = stubMapping;
		for (StubPostProcessor processor : processors) {
			stub = (T) processor.postProcess(stub, contract);
		}
		return stub;
	}

	/**
	 * Stub mapping to chose when no post processors where found on the classpath.
	 * @param stubMapping - mapping of a stub
	 * @param contract - contract for which stub was generated
	 * @return the converted stub mapping
	 */
	default T defaultStubMappingPostProcessing(T stubMapping, Contract contract) {
		return stubMapping;
	}

	/**
	 * @param inputFileName - name of the input file
	 * @return the name of the converted stub file. If you have multiple contracts in a
	 * single file then a prefix will be added to the generated file. If you provide the
	 * {@link Contract#getName} field then that field will override the generated file
	 * name.
	 *
	 * Example: name of file with 2 contracts is {@code foo.groovy}, it will be converted
	 * by the implementation to {@code foo.json}. The recursive file converter will create
	 * two files {@code 0_foo.json} and {@code 1_foo.json}
	 */
	String generateOutputFileNameForInput(String inputFileName);

	/**
	 * Describes the file extension of the generated mapping that this stub generator can
	 * handle.
	 * @return string describing the file extension
	 */
	default String fileExtension() {
		return ".json";
	}

}

同样,您必须提供spring.factories文件,如下所示 例:

# Stub converters
org.springframework.cloud.contract.verifier.converter.StubGenerator=\
org.springframework.cloud.contract.verifier.wiremock.DslToWireMockClientConverter

默认实现是 WireMock 存根生成。

您可以提供多个存根生成器实现。例如,从单个 DSL 中,您可以生成 WireMock 存根和 Pact 文件。

使用自定义 Stub Runner

如果您决定使用自定义存根生成,则还需要一种自定义的运行方式 stubs 替换为不同的 stub 提供程序。

假设您使用 Moco 构建存根,并且 您已经编写了一个存根生成器并将存根放在 JAR 文件中。

为了让 Stub Runner 知道如何运行您的存根,您必须定义一个自定义 HTTP 存根服务器实现,可能类似于以下示例:

import com.github.dreamhead.moco.bootstrap.arg.HttpArgs
import com.github.dreamhead.moco.runner.JsonRunner
import com.github.dreamhead.moco.runner.RunnerSetting
import groovy.transform.CompileStatic
import groovy.util.logging.Commons

import org.springframework.cloud.contract.stubrunner.HttpServerStub
import org.springframework.cloud.contract.stubrunner.HttpServerStubConfiguration

@Commons
@CompileStatic
class MocoHttpServerStub implements HttpServerStub {

	private boolean started
	private JsonRunner runner
	private int port

	@Override
	int port() {
		if (!isRunning()) {
			return -1
		}
		return port
	}

	@Override
	boolean isRunning() {
		return started
	}

	@Override
	HttpServerStub start(HttpServerStubConfiguration configuration) {
		this.port = configuration.port
		return this
	}

	@Override
	HttpServerStub stop() {
		if (!isRunning()) {
			return this
		}
		this.runner.stop()
		return this
	}

	@Override
	HttpServerStub registerMappings(Collection<File> stubFiles) {
		List<RunnerSetting> settings = stubFiles.findAll { it.name.endsWith("json") }
			.collect {
			log.info("Trying to parse [${it.name}]")
			try {
				return RunnerSetting.aRunnerSetting().addStream(it.newInputStream()).
					build()
			}
			catch (Exception e) {
				log.warn("Exception occurred while trying to parse file [${it.name}]", e)
				return null
			}
		}.findAll { it }
		this.runner = JsonRunner.newJsonRunnerWithSetting(settings,
			HttpArgs.httpArgs().withPort(this.port).build())
		this.runner.run()
		this.started = true
		return this
	}

	@Override
	String registeredMappings() {
		return ""
	}

	@Override
	boolean isAccepted(File file) {
		return file.name.endsWith(".json")
	}
}

然后,您可以在spring.factories文件,如下所示 示例显示:

org.springframework.cloud.contract.stubrunner.HttpServerStub=\
org.springframework.cloud.contract.stubrunner.provider.moco.MocoHttpServerStub

现在,您可以使用 Moco 运行 stub。

如果您未提供任何实现,则默认的 (WireMock) implementation 的 implementation 的 API 中。如果您提供多个,则使用列表中的第一个。

使用 Custom Stub Downloader

您可以通过创建StubDownloaderBuilder接口,如下例所示:

class CustomStubDownloaderBuilder implements StubDownloaderBuilder {

	@Override
	public StubDownloader build(final StubRunnerOptions stubRunnerOptions) {
		return new StubDownloader() {
			@Override
			public Map.Entry<StubConfiguration, File> downloadAndUnpackStubJar(
					StubConfiguration config) {
				File unpackedStubs = retrieveStubs();
				return new AbstractMap.SimpleEntry<>(
						new StubConfiguration(config.getGroupId(), config.getArtifactId(), version,
								config.getClassifier()), unpackedStubs);
			}

			File retrieveStubs() {
			    // here goes your custom logic to provide a folder where all the stubs reside
			}
		}
	}
}

然后,您可以在spring.factories文件,如下所示 示例显示:

# Example of a custom Stub Downloader Provider
org.springframework.cloud.contract.stubrunner.StubDownloaderBuilder=\
com.example.CustomStubDownloaderBuilder

现在,您可以选择包含存根源的文件夹。

如果未提供任何实现,则使用默认值(扫描 Classpath)。 如果您提供stubsMode = StubRunnerProperties.StubsMode.LOCALstubsMode = StubRunnerProperties.StubsMode.REMOTE,则使用 Aether 实现 如果您提供多个,则使用列表中的第一个。

使用 SCM 存根下载器

每当repositoryRoot从 SCM 协议开始 (目前,我们只支持git://),则存根下载程序会尝试 克隆存储库并将其用作合同源 生成测试或存根。

通过环境变量、系统属性或属性设置 在 plugin 或 contracts 仓库配置中,您可以 调整 Downloader 的行为。下表描述了可用的 性能:

表 1.SCM Stub Downloader 属性

属性的类型

属性名称

描述

* git.branch(插件属性)

* stubrunner.properties.git.branch(系统属性)

* STUBRUNNER_PROPERTIES_GIT_BRANCH(env 属性)

主人

要签出的分支

* git.username(插件属性)

* stubrunner.properties.git.username(系统属性)

* STUBRUNNER_PROPERTIES_GIT_USERNAME(env 属性)

Git 克隆用户名

* git.password(插件属性)

* stubrunner.properties.git.password(系统属性)

* STUBRUNNER_PROPERTIES_GIT_PASSWORD(env 属性)

Git 克隆密码

* git.no-of-attempts(插件属性)

* stubrunner.properties.git.no-of-attempts(系统属性)

* STUBRUNNER_PROPERTIES_GIT_NO_OF_ATTEMPTS(env 属性)

10

尝试将提交推送到的次数origin

* git.wait-between-attempts(插件属性)

* stubrunner.properties.git.wait-between-attempts(系统属性)

* STUBRUNNER_PROPERTIES_GIT_WAIT_BETWEEN_ATTEMPTS(env 属性)

1000

尝试将提交推送到之间等待的毫秒数origin