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

Consumer Driven Contracts

074c1726aee7c73df923c3d5064ab861?s=47 Pedro Tavares
September 28, 2017
700

Consumer Driven Contracts

074c1726aee7c73df923c3d5064ab861?s=128

Pedro Tavares

September 28, 2017
Tweet

Transcript

  1. CONSUMER DRIVEN CONTRACTS Boost your confidence with @ordepdev

  2. Testing is hard The pyramid is, most of the times,

    inverted.
  3. BUILDING MICROSERVICES WITHOUT BREAKING CONTRACTS IS EVEN HARDER

  4. MONOLITHS VS MICROSERVICES MONOLITH build test deploy

  5. MONOLITHS VS MICROSERVICES build test deploy build test deploy build

    test deploy microservices a b c
  6. HOW DO WE TEST MICROSERVICES?

  7. integration Mocking, Faking, Stubbing, you choose.

  8. INTEGRATION TESTS build test deploy We build our own stubs

    in order to make our tests pass a b
  9. end-to-end Those bloated and long to run… The feedback from

    this slide is already late.
  10. END-TO-END-TESTS build test deploy build test deploy build test deploy

    microservices END-TO-END Hard to write and maintain, slow to execute, and late feedback a b c
  11. What is wrong with this? Slow to run Very late

    feedback Hard to write and maintain Mocked stubs can be different from the reality Leads to passing tests and failing in production
  12. What is wrong with this? Slow to run Very late

    feedback Hard to write and maintain Mocked stubs can be different from the reality Leads to passing tests and failing in production
  13. What is wrong with this? Slow to run Very late

    feedback Hard to write and maintain Mocked stubs can be different from the reality Leads to passing tests and failing in production
  14. What is wrong with this? Slow to run Very late

    feedback Hard to write and maintain Mocked stubs can be different from the reality Leads to passing tests and failing in production
  15. What is wrong with this? Slow to run Very late

    feedback Hard to write and maintain Mocked stubs can be different from the reality Leads to passing tests and failing in production
  16. # Check if a person with a given age can

    drink beer curl -H "Content-Type: application/json" -X POST \ -d ‘{ “age”: 20 }' \ http://some.external.api:8090/check ~ provided by the producer at some point of time REAL LIFE EXAMPLE This snippet was taken from JIRA or some random Slack channel
  17. @Test public void check_withValidAge_shouldReturnHttpStatusOk() throws Exception { ResponseEntity<ResponsePayload> response =

    restTemplate.exchange( RequestEntity .put(URI.create("http://localhost:8080/chekc")) .contentType(MediaType.APPLICATION_JSON_UTF8) .body(new RequestPayload(10)), ResponsePayload.class); assertThat(response.getStatusCode()).isEqualByComparingTo(HttpStatus.OK); } TESTING on the consumer side
  18. @Test public void check_withValidAge_shouldReturnHttpStatusOk() throws Exception { ResponseEntity<ResponsePayload> response =

    restTemplate.exchange( RequestEntity .put(URI.create("http://localhost:8091/items/validate")) .contentType(MediaType.APPLICATION_JSON_UTF8) .body(new RequestPayload(10)), ResponsePayload.class); assertThat(response.getStatusCode()).isEqualByComparingTo(HttpStatus.OK); } check_withValidAge_shouldReturnHttpStatusOk OK TESTING on the consumer side
  19. @Test public void check_withValidAge_shouldReturnHttpStatusOk() throws Exception { ResponseEntity<ResponsePayload> response =

    restTemplate.exchange( RequestEntity .put(URI.create("http://localhost:8080/chekc")) .contentType(MediaType.APPLICATION_JSON_UTF8) .body(new RequestPayload(10)), ResponsePayload.class); assertThat(response.getStatusCode()).isEqualByComparingTo(HttpStatus.OK); } TESTING on the consumer side
  20. @Test public void check_withValidAge_shouldReturnHttpStatusOk() throws Exception { ResponseEntity<ResponsePayload> response =

    restTemplate.exchange( RequestEntity .put(URI.create("http://localhost:8080/chekc")) .contentType(MediaType.APPLICATION_JSON_UTF8) .body(new RequestPayload(10)), ResponsePayload.class); assertThat(response.getStatusCode()).isEqualByComparingTo(HttpStatus.OK); } TESTING on the consumer side
  21. @Test public void check_withValidAge_shouldReturnHttpStatusOk() throws Exception { ResponseEntity<ResponsePayload> response =

    restTemplate.exchange( RequestEntity .put(URI.create("http://localhost:8080/chekc")) .contentType(MediaType.APPLICATION_JSON_UTF8) .body(new RequestPayload(10)), ResponsePayload.class); assertThat(response.getStatusCode()).isEqualByComparingTo(HttpStatus.OK); } TESTING on the consumer side
  22. @Test public void check_withValidAge_shouldReturnHttpStatusOk() throws Exception { ResponseEntity<ResponsePayload> response =

    restTemplate.exchange( RequestEntity .put(URI.create("http://localhost:8080/chekc")) .contentType(MediaType.APPLICATION_JSON_UTF8) .body(new RequestPayload(10)), ResponsePayload.class); assertThat(response.getStatusCode()).isEqualByComparingTo(HttpStatus.OK); } TESTING on the consumer side
  23. after deploy…

  24. “An integration contract test is a test at the boundary

    of an external service verifying that it meets the contract expected by a consuming service” Martin Fowler Contract testing?
  25. “An integration contract test is a test at the boundary

    of an external service verifying that it meets the contract expected by a consuming service” Martin Fowler Contract testing?
  26. Expectations Contract Testing is all about… Shared between the producer

    and the consumer.
  27. SPRING CLOUD CONTRACT

  28. PRODUCER CONSUMER Common repository with contracts

  29. PRODUCER CONSUMER Creates a PR with the proposed contracts

  30. PRODUCER CONSUMER Review contracts and writes the missing implementation

  31. PRODUCER CONSUMER Merge PR with the proposed and validated contracts

  32. PRODUCER CONSUMER Contracts JAR gets deployed into Nexus CONSUMER

  33. STUBS ALWAYS MATCH SERVER-SIDE IMPLEMENTATION

  34. CHANGES ARE IMMEDIATELY VISIBLE ON BOTH SIDES

  35. Contract.make { request { method "POST" url "/check" body ([

    age: value(regex("[2-9][0-9]")) ]) headers { contentType(applicationJson()) } } response { status 200 body([ status: "OK" ]) headers { contentType(applicationJson()) } } } defining the contract
  36. Contract.make { request { method "POST" url "/check" body ([

    age: value(regex("[2-9][0-9]")) ]) headers { contentType(applicationJson()) } } response { status 200 body([ status: "OK" ]) headers { contentType(applicationJson()) } } } defining the contract
  37. @Test public void validate_validSizeShouldReturnHttpOk() throws Exception { // given: MockMvcRequestSpecification

    request = given() .header("Content-Type", "application/json") .body("{\"age\":\"20\"}"); // when: ResponseOptions response = given().spec(request) .post("/check"); // then: assertThat(response.statusCode()).isEqualTo(200); assertThat(response.header("Content-Type")).matches("application/json.*"); // and: DocumentContext parsedJson = JsonPath.parse(response.getBody().asString()); assertThatJson(parsedJson).field("['status']").isEqualTo("OK"); } generated tests
  38. @Test public void validate_validSizeShouldReturnHttpOk() throws Exception { // given: MockMvcRequestSpecification

    request = given() .header("Content-Type", "application/json") .body("{\"age\":\"20\"}"); // when: ResponseOptions response = given().spec(request) .post("/check"); // then: assertThat(response.statusCode()).isEqualTo(200); assertThat(response.header("Content-Type")).matches("application/json.*"); // and: DocumentContext parsedJson = JsonPath.parse(response.getBody().asString()); assertThatJson(parsedJson).field("['status']").isEqualTo("OK"); } generated tests
  39. @Test public void validate_validSizeShouldReturnHttpOk() throws Exception { // given: MockMvcRequestSpecification

    request = given() .header("Content-Type", "application/json") .body("{\"age\":\"20\"}"); // when: ResponseOptions response = given().spec(request) .post("/check"); // then: assertThat(response.statusCode()).isEqualTo(200); assertThat(response.header("Content-Type")).matches("application/json.*"); // and: DocumentContext parsedJson = JsonPath.parse(response.getBody().asString()); assertThatJson(parsedJson).field("['status']").isEqualTo("OK"); } generated testS
  40. { "request" : { "url" : "/check", "method" : "POST",

    "headers" : { "Content-Type" : { "matches" : "application/json.*" } }, "bodyPatterns" : [ { "matchesJsonPath" : "$[?(@.['age'] =~ /[2-9][0-9]/)]" } ] }, "response" : { "status" : 200, "body" : "{\"status\":\"OK\"}", "headers" : { "Content-Type" : "application/json" } } } generated stub
  41. { "request" : { "url" : "/check", "method" : "POST",

    "headers" : { "Content-Type" : { "matches" : "application/json.*" } }, "bodyPatterns" : [ { "matchesJsonPath" : "$[?(@.['age'] =~ /[2-9][0-9]/)]" } ] }, "response" : { "status" : 200, "body" : "{\"status\":\"OK\"}", "headers" : { "Content-Type" : "application/json" } } } generated stub
  42. CONSUMER TESTING WITH STUBS @RunWith(SpringRunner.class) @SpringBootTest(webEnvironment = WebEnvironment.MOCK) @AutoConfigureStubRunner( workOffline

    = true, ids = "com.example:beer-api-producer-external:+:stubs:8090" ) public class BeerControllerTest extends AbstractTest { @Test void should_give_me_a_beer_when_im_old_enough() { mockMvc.perform(MockMvcRequestBuilders.post("/beer") .contentType(MediaType.APPLICATION_JSON) .content(json.write(new Person("John Doe", 20)).getJson())) .andExpect(status().isOk()) .andExpect(content().string("THERE YOU GO")); } }
  43. CONSUMER TESTING WITH STUBS @RunWith(SpringRunner.class) @SpringBootTest(webEnvironment = WebEnvironment.MOCK) @AutoConfigureStubRunner( workOffline

    = true, ids = "com.example:beer-api-producer-external:+:stubs:8090" ) public class BeerControllerTest extends AbstractTest { @Test void should_give_me_a_beer_when_im_old_enough() { mockMvc.perform(MockMvcRequestBuilders.post("/beer") .contentType(MediaType.APPLICATION_JSON) .content(json.write(new Person("John Doe", 20)).getJson())) .andExpect(status().isOk()) .andExpect(content().string("THERE YOU GO")); } }
  44. A contract needs to speak the truth.

  45. How can we verify the truth?

  46. Running a test!

  47. CONFIDENCE Continuously evolve your codebase without breaking contracts

  48. Faster No need to setup end-to-end environments

  49. Less Error Prone Verification of Contracts are automatically managed

  50. Thank you. @ordepdev

  51. CONSUMER DRIVEN CONTRACTS Boost your confidence with @ordepdev