Skip to content

Commit 8216c90

Browse files
committed
first implementation of a like matcher that matches whole object hierarchies
1 parent fe9e15b commit 8216c90

File tree

9 files changed

+470
-1
lines changed

9 files changed

+470
-1
lines changed

.idea/modules/spring-boot-testing.iml

Whitespace-only changes.

pact/pact-feign-consumer/application.yml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,10 @@
11
userservice:
2+
ribbon:
3+
eureka:
4+
enabled: false
5+
listOfServers: localhost:8080
6+
7+
rootservice:
28
ribbon:
39
eureka:
410
enabled: false

pact/pact-feign-consumer/build.gradle

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,6 @@ dependencies {
2222
compile("org.springframework.cloud:spring-cloud-starter-feign:1.4.1.RELEASE")
2323
compile('com.h2database:h2:1.4.196')
2424
testCompile('org.codehaus.groovy:groovy-all:2.4.6')
25-
testCompile("au.com.dius:pact-jvm-consumer-junit_2.11:3.5.2")
25+
compile("au.com.dius:pact-jvm-consumer-junit_2.11:3.5.16")
2626
testCompile("org.springframework.boot:spring-boot-starter-test:${springboot_version}")
2727
}
Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
package io.reflectoring.dsl;
2+
3+
import java.lang.reflect.Field;
4+
import java.math.BigDecimal;
5+
import java.util.Arrays;
6+
import java.util.Collection;
7+
import java.util.HashSet;
8+
import java.util.Set;
9+
10+
import au.com.dius.pact.consumer.dsl.PactDslJsonBody;
11+
import au.com.dius.pact.consumer.dsl.PactDslJsonRootValue;
12+
13+
public class PactDslJsonBodyLikeMapper {
14+
15+
private static final Set<Class<?>> SIMPLE_TYPES = new HashSet<>(Arrays.asList(
16+
Boolean.class,
17+
boolean.class,
18+
Integer.class,
19+
int.class,
20+
Double.class,
21+
double.class,
22+
Float.class,
23+
float.class,
24+
BigDecimal.class,
25+
Number.class,
26+
String.class,
27+
Long.class,
28+
long.class
29+
));
30+
31+
public static PactDslJsonBody like(Object object) {
32+
return like(object, new PactDslJsonBody());
33+
}
34+
35+
public static PactDslJsonBody like(Object object, PactDslJsonBody body) {
36+
try {
37+
return recursiveLike(object, body);
38+
} catch (IllegalAccessException e) {
39+
throw new IllegalStateException("could not create PactDslJsonBody due to exception!", e);
40+
}
41+
}
42+
43+
private static PactDslJsonBody recursiveLike(Object object, PactDslJsonBody body) throws IllegalAccessException {
44+
Field[] fields = object.getClass().getDeclaredFields();
45+
for (Field field : fields) {
46+
field.setAccessible(true);
47+
48+
Object fieldValue = field.get(object);
49+
50+
if (fieldValue == null) {
51+
// fields with null values will not be mapped
52+
continue;
53+
}
54+
55+
if (isSimpleType(field.getType())) {
56+
mapSimpleFieldWithName(field.getName(), fieldValue, body);
57+
} else if (isCollectionType(field.getType())) {
58+
mapCollectionField(field.getName(), (Collection) fieldValue, body);
59+
} else {
60+
mapComplexField(field.getName(), fieldValue, body);
61+
}
62+
63+
}
64+
65+
return body;
66+
}
67+
68+
private static void mapSimpleFieldWithName(String fieldName, Object fieldValue, PactDslJsonBody body) throws IllegalAccessException {
69+
Class<?> type = fieldValue.getClass();
70+
if (String.class == type) {
71+
body.stringType(fieldName, (String) fieldValue);
72+
} else if (Boolean.class == type || boolean.class == type) {
73+
body.booleanType(fieldName, (Boolean) fieldValue);
74+
} else if (Integer.class == type || int.class == type || Long.class == type || long.class == type) {
75+
body.integerType(fieldName, (Integer) fieldValue);
76+
} else if (Double.class == type || double.class == type) {
77+
body.decimalType(fieldName, (Double) fieldValue);
78+
} else if (Float.class == type || float.class == type) {
79+
body.decimalType(fieldName, ((Float) fieldValue).doubleValue());
80+
} else if (BigDecimal.class == type) {
81+
body.decimalType(fieldName, (BigDecimal) fieldValue);
82+
} else if (Number.class.isAssignableFrom(type)) {
83+
body.numberType(fieldName, (Number) fieldValue);
84+
} else {
85+
throw new IllegalStateException(String.format("field '%s' of type '%s' is not a simple field", fieldName, type));
86+
}
87+
}
88+
89+
private static PactDslJsonRootValue getRootValueForType(Class<?> type) {
90+
if (String.class == type) {
91+
return PactDslJsonRootValue.stringType();
92+
} else if (Boolean.class == type || boolean.class == type) {
93+
return PactDslJsonRootValue.booleanType();
94+
} else if (Integer.class == type || int.class == type || Long.class == type || long.class == type) {
95+
return PactDslJsonRootValue.integerType();
96+
} else if (Double.class == type || double.class == type) {
97+
return PactDslJsonRootValue.decimalType();
98+
} else if (Float.class == type || float.class == type) {
99+
return PactDslJsonRootValue.decimalType();
100+
} else if (BigDecimal.class == type) {
101+
return PactDslJsonRootValue.decimalType();
102+
} else if (Number.class.isAssignableFrom(type)) {
103+
return PactDslJsonRootValue.numberType();
104+
} else {
105+
throw new IllegalStateException(String.format("unsupported type '%s'", type));
106+
}
107+
}
108+
109+
private static void mapCollectionField(String fieldName, Collection<?> collection, PactDslJsonBody body) throws IllegalAccessException {
110+
if (collection.isEmpty()) {
111+
throw new IllegalArgumentException("matching empty lists is not supported!");
112+
}
113+
114+
Class<?> listType = collection.iterator().next().getClass();
115+
116+
if (isSimpleType(listType)) {
117+
PactDslJsonRootValue rootValue = getRootValueForType(listType);
118+
body.eachLike(fieldName, rootValue);
119+
} else if (isCollectionType(listType)) {
120+
throw new IllegalArgumentException("collections of collections are not supported");
121+
} else {
122+
PactDslJsonBody nestedBody = body.eachLike(fieldName);
123+
for (Object complexObject : collection) {
124+
mapComplexFieldWithoutOpeningObject(complexObject, nestedBody);
125+
}
126+
nestedBody.closeObject().closeArray();
127+
}
128+
129+
}
130+
131+
private static void mapComplexField(String fieldName, Object fieldValue, PactDslJsonBody body) throws IllegalAccessException {
132+
PactDslJsonBody nestedBody = body.object(fieldName);
133+
mapComplexFieldWithoutOpeningObject(fieldValue, nestedBody);
134+
}
135+
136+
private static void mapComplexFieldWithoutOpeningObject(Object fieldValue, PactDslJsonBody nestedBody) throws IllegalAccessException {
137+
recursiveLike(fieldValue, nestedBody);
138+
nestedBody.closeObject();
139+
}
140+
141+
private static boolean isSimpleType(Class<?> type) {
142+
return SIMPLE_TYPES.contains(type);
143+
}
144+
145+
private static boolean isCollectionType(Class<?> type) {
146+
return Collection.class.isAssignableFrom(type);
147+
}
148+
149+
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
package io.reflectoring.dsl;
2+
3+
public class Nested {
4+
5+
private String stringField = "nested string";
6+
private Integer integerField = 42;
7+
private String nullField = null;
8+
9+
public String getStringField() {
10+
return stringField;
11+
}
12+
13+
public void setStringField(String stringField) {
14+
this.stringField = stringField;
15+
}
16+
17+
public Integer getIntegerField() {
18+
return integerField;
19+
}
20+
21+
public void setIntegerField(Integer integerField) {
22+
this.integerField = integerField;
23+
}
24+
25+
public String getNullField() {
26+
return nullField;
27+
}
28+
29+
public void setNullField(String nullField) {
30+
this.nullField = nullField;
31+
}
32+
}
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
package io.reflectoring.dsl;
2+
3+
import au.com.dius.pact.consumer.Pact;
4+
import au.com.dius.pact.consumer.PactProviderRuleMk2;
5+
import au.com.dius.pact.consumer.PactVerification;
6+
import au.com.dius.pact.consumer.dsl.PactDslJsonBody;
7+
import au.com.dius.pact.consumer.dsl.PactDslJsonRootValue;
8+
import au.com.dius.pact.consumer.dsl.PactDslWithProvider;
9+
import au.com.dius.pact.model.RequestResponsePact;
10+
import org.junit.Rule;
11+
import org.junit.Test;
12+
import org.junit.runner.RunWith;
13+
import org.springframework.beans.factory.annotation.Autowired;
14+
import org.springframework.boot.test.context.SpringBootTest;
15+
import org.springframework.test.context.junit4.SpringRunner;
16+
17+
@RunWith(SpringRunner.class)
18+
@SpringBootTest(properties = {
19+
// overriding provider address
20+
"rootservice.ribbon.listOfServers: localhost:8888"
21+
})
22+
public class PactDslJsonBodyLikeMapperConsumerTest {
23+
24+
@Rule
25+
public PactProviderRuleMk2 stubProvider = new PactProviderRuleMk2("testprovider", "localhost", 8888, this);
26+
27+
@Autowired
28+
private RootClient rootClient;
29+
30+
@Pact(state = "teststate", provider = "testprovider", consumer = "testclient")
31+
public RequestResponsePact createPact(PactDslWithProvider builder) {
32+
return builder
33+
.given("teststate")
34+
.uponReceiving("a POST request with a Root object")
35+
.path("/root")
36+
.method("POST")
37+
// .body(PactDslJsonBodyLikeMapper.like(new PactDslJsonBodyLikeMapperTest.Root()))
38+
.willRespondWith()
39+
.status(201)
40+
.matchHeader("Content-Type", "application/json")
41+
.body(PactDslJsonBodyLikeMapper.like(new Root()))
42+
.toPact();
43+
}
44+
45+
@Pact(state = "teststate2", provider = "testprovider", consumer = "testclient")
46+
public RequestResponsePact createPact2(PactDslWithProvider builder) {
47+
return builder
48+
.given("teststate2")
49+
.uponReceiving("a POST request with a Root object")
50+
.path("/root")
51+
.method("POST")
52+
.willRespondWith()
53+
.status(201)
54+
.matchHeader("Content-Type", "application/json")
55+
.body(new PactDslJsonBody()
56+
.eachLike("arrayField", PactDslJsonRootValue.numberType()))
57+
.toPact();
58+
}
59+
60+
@Test
61+
@PactVerification(fragment = "createPact")
62+
public void verifyPact() {
63+
rootClient.createRoot(new Root());
64+
}
65+
66+
@Test
67+
@PactVerification(fragment = "createPact2")
68+
public void verifyPact2() {
69+
rootClient.createRoot(new Root());
70+
}
71+
72+
}
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
package io.reflectoring.dsl;
2+
3+
4+
import au.com.dius.pact.consumer.dsl.PactDslJsonBody;
5+
import com.google.common.collect.ImmutableMap;
6+
import org.junit.Test;
7+
import static org.junit.Assert.*;
8+
9+
public class PactDslJsonBodyLikeMapperTest {
10+
11+
@Test
12+
public void createsMatchersForAllFields() {
13+
Root object = new Root();
14+
PactDslJsonBody jsonBody = PactDslJsonBodyLikeMapper.like(object);
15+
assertNoMatcher(jsonBody, ".nullField");
16+
assertMatcherType(jsonBody, ".stringField", "type");
17+
assertMatcherType(jsonBody, ".booleanField", "type");
18+
assertMatcherType(jsonBody, ".primitiveBooleanField", "type");
19+
assertMatcherType(jsonBody, ".integerField", "integer");
20+
assertMatcherType(jsonBody, ".primitiveIntegerField", "integer");
21+
assertMatcherType(jsonBody, ".doubleField", "decimal");
22+
assertMatcherType(jsonBody, ".primitiveDoubleField", "decimal");
23+
assertMatcherType(jsonBody, ".floatField", "decimal");
24+
assertMatcherType(jsonBody, ".primitiveFloatField", "decimal");
25+
assertMatcherType(jsonBody, ".bigDecimalField", "decimal");
26+
assertMatcherType(jsonBody, ".numberField", "number");
27+
assertMatcherType(jsonBody, ".nested.stringField", "type");
28+
assertMatcherType(jsonBody, ".nested.integerField", "integer");
29+
assertNoMatcher(jsonBody, ".nested.nullField");
30+
assertMatcherType(jsonBody, ".complexListField[*].stringField", "type");
31+
assertMatcherType(jsonBody, ".complexListField[*].integerField", "integer");
32+
assertMatcherType(jsonBody, ".simpleListField[*]", "integer");
33+
}
34+
35+
private void assertMatcherType(PactDslJsonBody jsonBody, String fieldName, String expectedMatcher) {
36+
assertMatcher(jsonBody, fieldName);
37+
assertEquals(String.format("expected matcher for field '%s' to be of type '%s'", fieldName, expectedMatcher),
38+
ImmutableMap.of("match", expectedMatcher),
39+
jsonBody.getMatchers().getMatchingRules().get(fieldName).getRules().get(0).toMap());
40+
}
41+
42+
private void assertMatcher(PactDslJsonBody jsonBody, String fieldName) {
43+
assertNotNull(String.format("expected a matcher for field '%s'", fieldName),
44+
jsonBody.getMatchers().getMatchingRules().get(fieldName));
45+
}
46+
47+
private void assertNoMatcher(PactDslJsonBody jsonBody, String fieldName) {
48+
assertNull(jsonBody.getMatchers().getMatchingRules().get(fieldName));
49+
}
50+
51+
}

0 commit comments

Comments
 (0)