Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Talk Creating and testing REST and Messaging ...

Talk Creating and testing REST and Messaging contracts with Spring Cloud Contract

REST and Messaging solutions do not come with an in-built contract compliance mechanism, which in many ways is a great thing. However, while working with microservice-based systems, it often appears that a practical mechanism that would provide help in shaping and describing REST and Messaging contracts would come in handy. Similarly, creating integration and acceptance tests in such systems presents many challenges.

In this talk, I present Spring Cloud Contract, a contract verification solution, written in big part in Groovy, that allows for both: easily shaping REST and Messaging contracts and verifying if our app adheres to them using automatically generated Spock tests. I will show how, using Spring Cloud Contract, we can quickly generate automatically-tested stubs from simple Groovy DSL scripts. I will talk about the typical usages and script examples, as well as possible problems and ways of handling them.

Olga Maciaszek-Sharma

July 27, 2018
Tweet

More Decks by Olga Maciaszek-Sharma

Other Decks in Technology

Transcript

  1. Creating and testing REST Creating and testing REST and Messaging

    contracts and Messaging contracts with Spring Cloud Contract with Spring Cloud Contract @olga_maciaszek Olga Maciaszek-Sharma 27.07.2018
  2. Fully Deployed Test Fully Deployed Test Environment Environment @olga_maciaszek Environment

    per Service Environment per Service One To Rule Them All One To Rule Them All
  3. Input A ==> Output B Input A ==> Output B

    @olga_maciaszek Contracts Contracts
  4. Contracts Contracts package contracts org.springframework.cloud.contract.spec.Contract.make { request { method 'PUT'

    url '/fraudcheck' body([ "client.id": $(regex('[0-9]{10}')), loanAmount: 99999 ]) headers { contentType('application/json') } } response { status OK() body([ fraudCheckStatus: "FRAUD", "rejection.reason": "Amount too high" ]) headers { contentType('application/json') } } } @olga_maciaszek
  5. Contracts Contracts request: method: PUT url: /fraudcheck body: "client.id": 1234567890

    loanAmount: 99999 headers: Content-Type: application/json matchers: body: - path: $.['client.id'] type: by_regex value: "[0-9]{10}" response: status: 200 body: fraudCheckStatus: "FRAUD" "rejection.reason": "Amount too high" headers: Content-Type: application/json;charset=UTF-8 @olga_maciaszek
  6. Gradle Setup - Producer Gradle Setup - Producer buildscript {

    repositories { mavenCentral() maven {url 'http://repo.spring.io/release'} } dependencies { classpath("org.springframework.cloud:spring-cloud-contract-gradle } } apply plugin: 'spring-cloud-contract' contracts { targetFramework = 'SPOCK' baseClassForTests = 'com.example.contractsdemo.FraudCheckTestBaseClass' } @olga_maciaszek
  7. Contract Tests Contract Tests def validate_shouldMarkClientAsFraud() { given: def request

    = given() .header('Content-Type', 'application/vnd.fraud.v1+json') .body('{"clientId":"12345678902","loanAmount":99999}') when: def response = given().spec(request).put("/fraudcheck") then: response.statusCode == 200 response.header('Content-Type') == 'application/vnd.fraud.v1+json' and: DocumentContext parsedJson = JsonPath.parse(response.body.asString()) assertTassertThatJson(parsedJson).field("rejectionReason") .isEqualTo("Amount too high") assertThatJson(parsedJson).field("fraudCheckStatus").isEqualTo("FRAUD") } @olga_maciaszek
  8. Stubs Stubs { "request": { "url": "/fraudcheck", "method": "PUT", "bodyPatterns":

    [ { "matchesJsonPath": "$[?(@.loanAmount == 99999)]" }, { "matchesJsonPath": "$[?(@.clientId == '12345678902')]" } ], "headers": { "Content-Type": { "equalTo": "application/vnd.fraud.v1+json" } } }, "response": { "status": 200, "body": "{\"fraudCheckStatus\":\"FRAUD\", \"rejectionReason\":\"Amount too high\"}", "headers": { "Content-Type": "application/vnd.fraud.v1+json" } } } @olga_maciaszek
  9. Gradle Setup - Consumer Gradle Setup - Consumer dependencies {

    testCompile('org.springframework.cloud:spring-cloud-starter-contract-stub-runner') } @olga_maciaszek
  10. Using Stubs Using Stubs @RunWith(SpringRunner.class) @SpringBootTest(webEnvironment=WebEnvironment.NONE) @AutoConfigureStubRunner(ids = {"com.example:http-server-dsl:+:stubs:6565"}, stubsMode

    = StubRunnerProperties.StubsMode.LOCAL) @DirtiesContext public class LoanApplicationServiceTests { ... } @olga_maciaszek @StubRunnerPort
  11. Using Stubs Using Stubs stubrunner: repositoryRoot: classpath:m2repo/repository/ ids: - org.springframework.cloud.contract.verifier.stubs:loanIssuance

    - org.springframework.cloud.contract.verifier.stubs:fraudDetectionServer - org.springframework.cloud.contract.verifier.stubs:bootService stubs-mode: remote @olga_maciaszek @ContextConfiguration(classes = Config, loader = SpringBootContextLoader) @SpringBootTest(properties = [" stubrunner.cloud.enabled=false"]) @AutoConfigureStubRunner @DirtiesContext @ActiveProfiles("test") class StubRunnerConfigurationSpec extends Specification { @Autowired StubFinder stubFinder def 'should start WireMock servers'() { expect: 'WireMocks are running' stubFinder.findStubUrl('org.verifier.stubs', 'loanIssuance') != null }
  12. Using Stubs Using Stubs @Rule public StubRunnerRule rule = new

    StubRunnerRule() .downloadStub("com.example","fraud-service") .stubsMode(StubRunnerProperties.StubsMode.LOCAL) .withStubPerConsumer(true) .withConsumerName("loan-service"); int fraudServicePort = rule.findStubUrl("fraud-service").getPort() @olga_maciaszek
  13. Messaging Contracts Messaging Contracts def contractDsl = Contract.make { label

    'some_label' input { messageFrom('inputQueue') messageBody([ bookName: 'foo' ]) messageHeaders { header('sample', 'header') } } outputMessage { sentTo('outputQueue') body([ bookName: 'foo' ]) headers { header('BOOK-NAME', 'foo') } } } @olga_maciaszek
  14. Messaging Contract Tests Messaging Contract Tests given: ContractVerifierMessage inputMessage =

    contractVerifierMessaging.create( '''{"bookName":"foo"}''', ['sample': 'header'] ) when: contractVerifierMessaging.send(inputMessage, 'inputQueue') then: ContractVerifierMessage response = contractVerifierMessaging .receive('outputQueue') assert response != null response.getHeader('BOOK-NAME')?.toString() == 'foo' and: DocumentContext parsedJson = JsonPath.parse(contractVerifierObjectMapper .writeValueAsString(response.payload)) assertThatJson(parsedJson).field("bookName").isEqualTo("foo") @olga_maciaszek
  15. Messaging Client-Side Test Messaging Client-Side Test given: messaging.send(new BookReturned('foo'), [sample:

    'header'], 'input') when: Message<?> receivedMessage = messaging.receive('outputTest') then: receivedMessage != null assertJsons(receivedMessage.payload) receivedMessage.headers.get('BOOK-NAME') == 'foo' @olga_maciaszek
  16. Public API Public API Spring Rest Docs Spring Rest Docs

    MockMvc Tests -> REST Docs, Stubs, Contracts @olga_maciaszek
  17. Contracts Scope Contracts Scope Skip Contracts for non- Skip Contracts

    for non- essential functionality essential functionality @olga_maciaszek
  18. Skip Multiple Values Skip Multiple Values @olga_maciaszek request { method

    'PUT' url '/fraudcheck' body([ "client.id": $(regex('[0-9]{10}')), "client.type" : "INDIVIDUAL" loanAmount: 99999 ]) headers { contentType('application/json') }} request { method 'PUT' url '/fraudcheck' body([ "client.id": $(regex('[0-9]{10}')), "client.type" : "BUSINESS" loanAmount: 99999 ]) headers { contentType('application/json') }}
  19. Spring Cloud Contract at Spring Cloud Contract at Devskiller -

    case study Devskiller - case study blogpost blogpost @olga_maciaszek