Spring Cloud Contract
本文展示了使用Spring Cloud Contract,实现契约测试的必要代码。
Provider端:
首先配置Contract相关依赖
build.gradle
buildscript {
...
dependencies {
...
classpath("org.springframework.cloud:spring-cloud-contract-gradle-plugin:2.0.1.RELEASE")
}
}
...
apply plugin: 'spring-cloud-contract'
...
dependencies {
testCompile('org.springframework.cloud:spring-cloud-starter-contract-verifier')
}
contracts {
baseClassForTests = "com.thoughtworks.contract.ProductBaseTest"
}
...
如果项目中存在多个controller,需要分别对各个controller进行测试,则需要将contracts任务进行修改
contracts {
packageWithBaseClasses = "com.thoughtworks.contract.provider"
}
然后创建相应实体名称的test基类(eg:ProductBase),在test/resouces/contracts目录下创建不同实体的Dir储存不同实体的请求。
在测试目录下创建基类测试文件
ProductBaseTest
@RunWith(SpringRunner.class)
@SpringBootTest(classes = ContractApplication.class, webEnvironment = SpringBootTest.WebEnvironment.MOCK)
@DirtiesContext
@AutoConfigureMessageVerifier
public abstract class ProductBaseTest {
@Autowired
private ProductController productController;
@Before
public void setup() {
StandaloneMockMvcBuilder standaloneMockMvcBuilder = MockMvcBuilders.standaloneSetup(productController);
RestAssuredMockMvc.standaloneSetup(standaloneMockMvcBuilder);
}
}
这里需要注意将基类声明为abstract class,不这样做在运行测试时会对当前测试文件报错:java.lang.Exception: No runnable methods
创建stubs
在test的resources目录下创建contracts目录,并在其中添加stubs文件,编写stubs可以使用两种方式进行编写:yaml,groovy。
yaml
name: "should_return_all_products_yaml"
request:
url: /products
method: GET
response:
status: 200
bodyFromFile: response.json
headers:
Content-Type: application/json;charset=UTF-8
groovy
import org.springframework.cloud.contract.spec.Contract
Contract.make {
name "should_return_all_products_groovy"
request {
method GET()
url("/products")
}
response {
headers{
Content-Type: application/json;charset=UTF-8
}
body(file("response.json"))
status(200)
}
}
注意到本文都是通过在contracts目录下创建对应的json文件,作为body的输入。
添加DB-Rider
添加DB-Rider使得在测试时,使用DB-Rider生成的数据库数据进行测试,防止原始数据库在测试阶段被修改。
首先添加依赖
testCompile('com.github.database-rider:rider-spring:1.2.9') {
exclude group: 'org.slf4j', module: 'slf4j-simple'
}
在ProductBaseTest中添加DB-rider的相关注解
@RunWith(SpringRunner.class)
@SpringBootTest(classes = ContractApplication.class, webEnvironment = SpringBootTest.WebEnvironment.MOCK)
@DBRider
@ActiveProfiles("test")
@DBUnit(caseSensitiveTableNames = true)
@DataSet("product.yml")
public abstract class ProductBaseTest {
@Autowired
private ProductController productController;
@Before
public void setup() {
StandaloneMockMvcBuilder standaloneMockMvcBuilder = MockMvcBuilders.standaloneSetup(productController);
RestAssuredMockMvc.standaloneSetup(standaloneMockMvcBuilder);
}
}
然后在test/resources目录下的创建datasets文件夹,在里面使用yaml格式添加自定义的数据库数据
product:
- id: 1
name: "苹果"
- id: 2
name: "笔记本电脑"
- id: 3
name: "电视机"
nexus
为了使producer端生成的契约jar包和相关文件能够发布到共有仓库,以便consumer端能够调用,使用docker创建nexus的容器
docker pull sonatype/nexus
docker run -d -p 8081:8081 --name nexus sonatype/nexus
然后将生成的契约文件自动部署到nexus的容器中,在进行自动部署时,需要在build.gradle中配置依赖
...
apply plugin: 'maven-publish'
...
publishing {
repositories {
maven {
url 'http://localhost:8081/nexus/content/repositories/snapshots/'
credentials {
username = 'admin'
password = 'admin123'
}
}
}
}
测试
完成代码编写后,通过运行./gradlew clean build即可自动进行契约测试,contract插件将自动在build/generated-test-sources目录下生成对应的测试文件,进行测试。
发布
运行./gradlew publish将build生成的stubs自动发布到nexus上,供consumer端进行调用。
完整示例代码地址:https://github.com/FeiXie-Liam/contract-start-provider
consumer端
配置相关依赖
testCompile ('org.springframework.cloud:spring-cloud-starter-contract-stub-runner')
添加nexus配置
在测试环境的application-test.yml文件中添加配置:
stubrunner:
ids: com.thoughtworks:contract:+:stubs:8998
repositoryRoot: http://localhost:8081/nexus/content/repositories/snapshots/
需要注意的是在一台电脑上运行provider和consumer服务时,当provider端已经通过某个端口(eg.8090)运行起来时,如果consumer端想要进行契约测试,stubrunner的ids端口不能与provider端的端口相同,否则测试程序会报错Address already in use
,需要将其指定为未被占用的端口。
编写测试文件
@RunWith(SpringRunner.class)
@SpringBootTest(classes = ConsumerApplication.class)
@AutoConfigureStubRunner(stubsMode = StubRunnerProperties.StubsMode.REMOTE)
@ActiveProfiles("test")
public class ConsumerTest {
@Autowired
ProductService productService;
@Test
public void should_return_all_products() {
//given
//when
List<Product> actual = productService.getAll();
//then
...
}
}
注意在测试文件的类名上添加stub runner的注解@AutoConfigureStubRunner(stubsMode = StubRunnerProperties.StubsMode.LOCAL)
stubsMode包含LOCAL, CLASSPATH, REMOTE三种方式。
- LOCAL:线下仓库地址 eg. localhost
- CLASSPATH:项目目录下的问题
- REMOTE:远程仓库的地址 eg.亚马逊云
完整代码示例:https://github.com/FeiXie-Liam/contract-start-consumer