Skip to content

Commit

Permalink
cdc support springMVC springCloud
Browse files Browse the repository at this point in the history
  • Loading branch information
lmx1989219 committed Dec 21, 2018
1 parent b73747d commit 769d21f
Show file tree
Hide file tree
Showing 10 changed files with 193 additions and 66 deletions.
9 changes: 8 additions & 1 deletion pact-demo-consumer/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,14 @@
<java.version>1.8</java.version>
</properties>
<dependencies>

<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-netflix-core</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
package com.lmx.pactdemoconsumer;

import au.com.dius.pact.consumer.dsl.PactDslJsonBody;

import java.lang.reflect.Field;
import java.math.BigDecimal;
import java.util.Date;

public class PactDslJsonBodyUtil {
/**
* 以req对象来构建期望的请求
* <p>
* 定义格式规范:如日期
* </p>
*
* @param o
* @param pactDslJsonBody
*/
public static PactDslJsonBody buildReqJson(PactDslJsonBody pactDslJsonBody, Object o) throws Exception {
for (Field field : o.getClass().getDeclaredFields()) {
if (field.getType().isInstance(new String())) {
field.setAccessible(true);
pactDslJsonBody.stringValue(field.getName(), (String) field.get(o));
} else if (field.getType().isInstance(new Integer(1))
|| field.getType().isInstance(new Long(1))
|| field.getType().isInstance(new BigDecimal(1)))
pactDslJsonBody.numberType(field.getName());
else if (field.getType().isInstance(new Date()))
pactDslJsonBody.date(field.getName(), "yyyy-MM-dd HH:mm:ss");
else if (field.getType().newInstance() instanceof Object) {
PactDslJsonBody innerBodyObj = pactDslJsonBody.object(field.getName());
field.setAccessible(true);
Object innerObj = field.get(o);
return buildReqJson(innerBodyObj, innerObj);
}
}
return pactDslJsonBody;

}

/**
* 正则匹配响应值
* <p>
* 定义格式规范:如日期和初始值
* </p>
*
* @param cls
* @param pactDslJsonBody
*/
public static void buildRespJson(Class cls, PactDslJsonBody pactDslJsonBody) {
for (Field field : cls.getDeclaredFields()) {
if (field.getType().isInstance(new String()))
pactDslJsonBody.stringMatcher(field.getName(), "^[A-Za-z0-9]+$", "nZroXQogwHTRfpsyCZ98");
if (field.getType().isInstance(new Integer(1))
|| field.getType().isInstance(new Long(1))
|| field.getType().isInstance(new BigDecimal(1)))
pactDslJsonBody.numberType(field.getName());
if (field.getType().isInstance(new Date()))
pactDslJsonBody.date(field.getName(), "yyyy-MM-dd HH:mm:ss", new Date());
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package com.lmx.pactdemoconsumer;

import org.springframework.cloud.netflix.feign.FeignClient;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;

@FeignClient("sc.provider")
public interface PactFeignClient {

@RequestMapping(value = "/api/pact",method = RequestMethod.POST)
PactHttp.Resp hello(PactHttp.Req body);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
package com.lmx.pactdemoconsumer;

import au.com.dius.pact.consumer.ConsumerPactBuilder;
import au.com.dius.pact.consumer.PactVerificationResult;
import au.com.dius.pact.consumer.dsl.PactDslJsonBody;
import au.com.dius.pact.model.MockProviderConfig;
import au.com.dius.pact.model.RequestResponsePact;
import com.alibaba.fastjson.JSONObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.cloud.netflix.feign.FeignClient;
import org.springframework.web.bind.annotation.RequestMapping;

import java.io.IOException;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

import static au.com.dius.pact.consumer.ConsumerPactRunnerKt.runConsumerTest;
import static org.junit.Assert.assertEquals;

/**
* 契约测试执行器
* <p>
* 目标类:feignClient
*/
public class PactFeignClientInvoker implements InvocationHandler {
private Logger logger = LoggerFactory.getLogger(getClass());

private static PactFeignClientInvoker pactInvoker = new PactFeignClientInvoker();

@Override
public Object invoke(Object proxy, Method method, Object[] args) {
FeignClient cdc = method.getDeclaringClass().getDeclaredAnnotation(FeignClient.class);
RequestMapping cdcInfo = method.getDeclaredAnnotation(RequestMapping.class);
Object reqBody = args[0];
PactDslJsonBody req = new PactDslJsonBody();
PactDslJsonBody req_ = null;
try {
req_ = PactDslJsonBodyUtil.buildReqJson(req, reqBody);
} catch (Exception e) {
logger.error("", e);
}
PactDslJsonBody resp = new PactDslJsonBody();
PactDslJsonBodyUtil.buildRespJson(method.getReturnType(), resp);
RequestResponsePact pact = ConsumerPactBuilder
.consumer(System.getProperty("spring.application.name"))
.hasPactWith(cdc.value())
.uponReceiving("it's a feign api")
.path(cdcInfo.value()[0])
.method(cdcInfo.method() == null ? "POST" : cdcInfo.method()[0].toString())
.body(req_)
.willRespondWith()
.status(200)
.body(resp)
.toPact();
MockProviderConfig config = MockProviderConfig.createDefault();
StringBuilder stringBuilder = new StringBuilder();
PactVerificationResult result = runConsumerTest(pact, config, mockServer -> {
try {
org.json.JSONObject jsonObject = new PactProviderClient(mockServer.getUrl()).pactMock((org.json.JSONObject) req.getBody(), cdcInfo.value()[0]);
assertEquals(jsonObject.toString(), resp.getBody().toString());
stringBuilder.append(jsonObject.toString());
} catch (IOException e) {
throw new RuntimeException(e);
}
});

if (result instanceof PactVerificationResult.Error) {
throw new RuntimeException(((PactVerificationResult.Error) result).getError());
}
assertEquals(PactVerificationResult.Ok.INSTANCE, result);
return JSONObject.parseObject(stringBuilder.toString(), method.getReturnType());
}

public static Object getProxyObj(Class interface_) {
return Proxy.newProxyInstance(PactFeignClientInvoker.class.getClassLoader(), new Class[]{interface_}, pactInvoker);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
import java.util.Date;

/**
* 模拟feignClient
* 适用于restTemplate,httpClient这样没有客户端代码的情况
*/
@Cdc(provider = "Some Provider", consumer = "Some Consumer", reqDesc = "hello pact")
public interface PactHttp {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,20 +10,17 @@
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.math.BigDecimal;
import java.util.Date;

import static au.com.dius.pact.consumer.ConsumerPactRunnerKt.runConsumerTest;
import static org.junit.Assert.assertEquals;

/**
* 契约测试执行器
* <p>
* 目标类:feignClient,用于模拟暂时自己定义驱动注解
* 目标类:模拟restTemplate,httpClient的请求响应
*/
public class PactInvoker implements InvocationHandler {
private Logger logger = LoggerFactory.getLogger(getClass());
Expand All @@ -38,12 +35,12 @@ public Object invoke(Object proxy, Method method, Object[] args) {
PactDslJsonBody req = new PactDslJsonBody();
PactDslJsonBody req_ = null;
try {
req_ = buildReqJson(req, reqBody);
req_ = PactDslJsonBodyUtil.buildReqJson(req, reqBody);
} catch (Exception e) {
logger.error("", e);
}
PactDslJsonBody resp = new PactDslJsonBody();
buildRespJson(method.getReturnType(), resp);
PactDslJsonBodyUtil.buildRespJson(method.getReturnType(), resp);
RequestResponsePact pact = ConsumerPactBuilder
.consumer(cdc.consumer())
.hasPactWith(cdc.provider())
Expand Down Expand Up @@ -77,57 +74,4 @@ public Object invoke(Object proxy, Method method, Object[] args) {
public static Object getProxyObj(Class interface_) {
return Proxy.newProxyInstance(PactInvoker.class.getClassLoader(), new Class[]{interface_}, pactInvoker);
}

/**
* 以req对象来构建期望的请求
* <p>
* 定义格式规范:如日期
* </p>
*
* @param o
* @param pactDslJsonBody
*/
PactDslJsonBody buildReqJson(PactDslJsonBody pactDslJsonBody, Object o) throws Exception {
for (Field field : o.getClass().getDeclaredFields()) {
if (field.getType().isInstance(new String())) {
field.setAccessible(true);
pactDslJsonBody.stringValue(field.getName(), (String) field.get(o));
} else if (field.getType().isInstance(new Integer(1))
|| field.getType().isInstance(new Long(1))
|| field.getType().isInstance(new BigDecimal(1)))
pactDslJsonBody.numberType(field.getName());
else if (field.getType().isInstance(new Date()))
pactDslJsonBody.date(field.getName(), "yyyy-MM-dd HH:mm:ss");
else if (field.getType().newInstance() instanceof Object) {
PactDslJsonBody innerBodyObj = pactDslJsonBody.object(field.getName());
field.setAccessible(true);
Object innerObj = field.get(o);
return buildReqJson(innerBodyObj, innerObj);
}
}
return pactDslJsonBody;

}

/**
* 正则匹配响应值
* <p>
* 定义格式规范:如日期和初始值
* </p>
*
* @param cls
* @param pactDslJsonBody
*/
void buildRespJson(Class cls, PactDslJsonBody pactDslJsonBody) {
for (Field field : cls.getDeclaredFields()) {
if (field.getType().isInstance(new String()))
pactDslJsonBody.stringMatcher(field.getName(), "^[A-Za-z0-9]+$", "nZroXQogwHTRfpsyCZ98");
if (field.getType().isInstance(new Integer(1))
|| field.getType().isInstance(new Long(1))
|| field.getType().isInstance(new BigDecimal(1)))
pactDslJsonBody.numberType(field.getName());
if (field.getType().isInstance(new Date()))
pactDslJsonBody.date(field.getName(), "yyyy-MM-dd HH:mm:ss", new Date());
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
import java.util.Map;

/**
* 服务提供端mock接口
* mock provider
*/
public class PactProviderClient {

Expand All @@ -25,7 +25,7 @@ public org.json.JSONObject pactMock(org.json.JSONObject body, String path) throw
org.json.JSONObject resp = new org.json.JSONObject();
JSONObject json = JSONObject.parseObject(response);
for (Map.Entry e : json.entrySet())
resp.put(e.getKey().toString(),e.getValue());
resp.put(e.getKey().toString(), e.getValue());
return resp;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
@Slf4j
public class PactConsumerTest {
/**
* 契约测试遵循字段严格匹配的约定
* simple demo
*/
@Test
public void testPact() {
Expand Down Expand Up @@ -72,12 +72,25 @@ public void testPact() {
}

/**
* 契约测试遵循字段类型即可,当然也可以使用正则等
* support restTemplate/httpClient pact testing
*
*/
@Test
public void testProxyPact() {
PactHttp pactHttp = (PactHttp) PactInvoker.getProxyObj(PactHttp.class);
PactHttp.Resp resp = pactHttp.hello(new PactHttp.Req("james", "123", 100L, new Date(), new PactHttp.InnerReq("15821303235", "[email protected]")));
PactHttp.Resp resp = pactHttp.hello(new PactHttp.Req("james", "123", 100L, new Date(),
new PactHttp.InnerReq("15821303235", "[email protected]")));
log.info("cdc resp={}", resp);
}

/**
* support feignClient pact testing
*/
@Test
public void testProxyFeignPact() {
PactFeignClient feignClient = (PactFeignClient) PactFeignClientInvoker.getProxyObj(PactFeignClient.class);
PactHttp.Resp resp = feignClient.hello(new PactHttp.Req("james", "123", 100L, new Date(),
new PactHttp.InnerReq("15821303235", "[email protected]")));
log.info("cdc resp={}", resp);
}
}
6 changes: 6 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,12 @@
<scope>test</scope>
</dependency>

<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-netflix-core</artifactId>
<version>1.2.4.RELEASE</version>
</dependency>

<dependency>
<groupId>au.com.dius</groupId>
<artifactId>pact-jvm-consumer-junit_2.12</artifactId>
Expand Down
4 changes: 4 additions & 0 deletions readme.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
**pact testing demo with maven project**

##features
1. support springMVC
2. support springCloud

>install broker
https://github.com/pact-foundation/pact_broker/tree/master/example
Expand Down

0 comments on commit 769d21f

Please sign in to comment.