Contract Driven Development

APIs perform a number of behaviors; retrieve data, add data, delete data, etc.. What exactly a request and response looks like can be generally described, but as developers we need explicit details. While a human can recognize "firstName" and "fName" as the same things, a computer will not. By defining the behaviors of an API in a contract consumers (clients) and producers can build and test against the contracts and have confidence that their code will work when it is working with the real application.

In this section we will walk through the process of writing a new contract and updating an existing one. After completing this section, you can the walk through the process of validating the contracts and running the mock service as either a client-side developer or a server-side developer.

Fork and Clone Contracts Repo

The first steps is to fork the contracts repository located here: https://github.com/wkorando/produce-contracts

Once you have forked the repository clone it. From the root of produce-contracts navigate to META-INF/com.ibm.developer/produce-service/0.0.1-SNAPSHOT/contracts the contract we will be adding and updating will be in this folder.

Writing a Contract to Define Error Behavior

Contracts shouldn't just be covering the golden path. APIs will also be sent invalid requests as well. It's important that we also define how our API behaves in these scenarios so that clients can properly handle responses to invalid requests.

Create a new file named: findProduceByNameInvalidCharacter.yml

In the file add the following:

name: find-produce-by-name-invalid-character
description: Find find all produce that matches supplied name
priority: 1
request:
  method: GET
  url: /api/v1/produce/+
  matchers:
    url:
      regex: "/api/v1/produce/^\\W+$"
response:
  status: 400
  body:
    errorMessage: "Produce name must be alpha numeric!"
  headers:
    Content-Type: application/json;charset=UTF-8

Update Contract to Respond with Values in a Request

Contracts don't have to be entirely static. While it is important that the contracts behave in a predictable way, having some dynamic behavior can make them easier to test with or better able to simulate real world behavior. One way Spring Cloud Contract (SCC) to accomplish this is by sending in the response values from a request.

SCC has a several pre-built functions. To access this functionality you will use triple handle bars {{{to invoke these functions. Using jsonPath, fields within a request body can be referenced. If we wanted to reference the name field it would look like this:

name: "{{{ jsonpath this '$.name' }}}"

Let's update the addProduce.yml contract so the response is matches the request:

name: add-produce
description: Operation for adding a new produce item
request:
  method: POST
  url: /api/v1/produce
  headers:
    Content-Type: application/json;charset=UTF-8
  body:
    name: "Kiwi"
    subName: ""
    quantity: 75
  matchers:
    headers:
    - key: Content-Type
      regex: "application/json.*"
    body:
      - path: $.name
        type: by_regex
        predefined: only_alpha_unicode
      - path: $.subName
        type: by_regex
        predefined: only_alpha_unicode
      - path: $.quantity
        type: by_regex
        value: "[0-9]+"
response:
  status: 200
  body:
    id: 10
    name: "{{{ jsonpath this '$.name' }}}"
    subName: "{{{ jsonpath this '$.subName' }}}"
    quantity: "{{{ jsonpath this '$.quantity' }}}"
  headers:
    Content-Type: application/json;charset=UTF-8
  matchers:
    body:
      - path: $.id
        type: by_regex
        value: "[0-9]+"

Commit and Push Changes

Once you have made all the changes be sure to commit and push those changes to your github repo.

git add .
git commit -m "adding contracts"
git push

Last updated

Was this helpful?