Consumer
Driven
Contracts
Jędrzej Andrykowski
JUG Zielona Góra 21.12.2016
Które testy będą odpowiednie?
Dlaczego Consumer-Driven Contracts?
Jak wykorzystać Spring Cloud Contract Verifier?
Request
Response
Client
Server
Jak przetestować?
Testy
jednostkowe
Testy akceptacyjne
?
Co z komunikacjÄ…?
Dlaczego powinniśmy ją testować?
Request
Response
Client
Server
Client
Server
POST /users
1.0.0
1.2.3
201
POST /users
201
Client
Server
POST /users
1.1.0
1.2.3
POST /users
400
Może End2End?
Ale...
Czasochłonne
Ciężkie
Kruche
BlokujÄ…ce
PowiÄ…zane
Mało wiarygodne
Można lepiej...
                      Dlaczego
Consumer-Driven
       Contracts?
...zgodne porozumienie dwóch lub więcej stron ustalające ich wzajemne prawa lub obowiązki...
Kontrakt
Client
Server
POST /users
1.0.0
1.2.3
201
POST /users
201
IF
THEN
Kontrakt pomiędzy klientem a serwerem
1.2.3
1.0.0
1.1.0
1.0.0
1.1.0
Kontakt ustalony przez serwer
Kontrakt sterowany potrzebami konsumenta
1.2.3
1.2.3
1.2.3
Czy to oznacza, że serwer nie ma już nic do gadania???
Kontrakty są tematami do rozmów i ustaleń
Sukces serwera jest uzależniony od jego poprawnego konsumowania
Implementacja
Arkusz lub dokument
Klient pisze testy serwerowi
PULL
PUSH
Jak to połączyć?
Jak wykorzystać
Jak wykorzystać
Spring Cloud
Contract Verifier
Contract
PUSH
STUBS
REPOSITORY
Stubs
(JSON)
Server Tests
Spring Cloud
Contract Verifier
Wiremock
Stubs
  Runs server tests
Â
Â
PULL
Client
Server
GENERATE
GENERATE
  Runs client tests
Â
Â
PUSH
PULL
Serwer
org.springframework.cloud.contract.spec.Contract.make {
request {
method 'POST'
urlPath('/users')
headers {
header 'Content-Type': 'application/json'
}
body(
name: $(client(regex('[a-zA-Z]+')), server('Jan')),
)
}
response {
status 201
body(
id: $(client('123'), server(regex('\\d+'))),
name: $(client('Jan'), server(regex('[a-zA-Z]+')))
)
headers {
header 'Content-Type': 'application/json'
}
}
}
Definiujemy kontrakt
class ServerSpec extends Specification {
UserService usersServiceMock = Stub(UserService) {
createUser(_) >> new User(123, "Jan")
}
def setup() {
def userController = new UserRestController(usersServiceMock)
RestAssuredMockMvc.standaloneSetup(userController)
}
}
Definiujemy bazową klasę testów
buildscript {
repositories {
mavenCentral()
}
dependencies {
classpath "org.springframework.cloud:spring-cloud-contract-gradle-plugin:1.0.2.RELEASE"
}
}
...
contracts {
targetFramework = 'Spock'
contractsDslDir = file('./cdc-contracts')
generatedTestSourcesDir = file('./build/generated-test-sources')
baseClassForTests = 'pl.jug.cdc.server.ServerSpec'
basePackageForTests = 'pl.jug.cdc.server.contracts'
}
Podstawowa konfiguracja - Gradle
./gradlew clean build
class ContractVerifierSpec extends ServerSpec {
def validate_createNewUser() throws Exception {
given:
def request = given().header("Content-Type", "application/json")
.body('''{"name":"Jan"}''')
when:
def response = given().spec(request)
.post("/users")
then:
response.statusCode == 201
response.header('Content-Type') == 'application/json'
and:
DocumentContext parsedJson = JsonPath.parse(response.body.asString())
assertThatJson(parsedJson).field("id").matches("\\d+")
assertThatJson(parsedJson).field("name").matches("[a-zA-Z]+")
}
}
Wygenerowany test dla serwera
{
"uuid" : "c2cef9bb-e1f0-4bb6-aaf2-e02c034fcc47",
"request" : {
"urlPath" : "/users",
"method" : "POST",
"headers" : {
"Content-Type" : {
"equalTo" : "application/json"
}
},
"bodyPatterns" : [ {
"matchesJsonPath" : "$[?(@.name =~ /[a-zA-Z]+/)]"
} ]
},
"response" : {
"status" : 201,
"body" : "{\"id\":\"123\",\"name\":\"Jan\"}",
"headers" : {
"Content-Type" : "application/json"
}
}
}
Wygenerowany stub dla klienta
Klient
dependencies {
testCompile 'org.springframework.cloud:spring-cloud-starter-contract-stub-runner:1.0.2.RELEASE'
}
Podstawowa konfiguracja - Gradle
@RunWith(SpringRunner.class)
@DirtiesContext
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.MOCK)
@AutoConfigureStubRunner( workOffline = true, ids = {"pl.jug.cdc:server:+:stubs:8090"})
public class UserServiceTest {
@Autowired
private UserService userService;
@Test
public void shouldCreateNewUser() {
//given
//when
final UserResponse result = userService.createNewUser("Jan");
//then
assertThat(result.getId()).isNotNull();
assertThat(result.getName()).isNotBlank();
}
}
Test klienta
Nowości
Więcej o CDC...