← Driving your service: HTTP, gRPC, a browser and a shell

Driving your service over HTTP

clientshttp
A scenario tells a client what to do; the client uses the service the way a person or another system would, and the scenario checks what came back.tomato runreads the scenario, drives the client, checks the answerthe scenarioWhensends "POST" to …Thenresponse status is …features/*.feature"todos-client"http-client resourcedoesPOST /api/todos{"title": "Buy milk"}gets back201{"id": 1, "title": "Buy milk"}todosyour serviceHTTP API :3000gRPC API :50051web page /CLI todo add …

Start. tomato starts your service and its dependencies. The client only needs to know where the service is: an address, or nothing at all for the shell.

Act. A When step tells the client what to do, and it does it the way another service would: it sends a request.

Answer. The service answers through the same door: a status, headers and a body.

Assert. Then steps check that answer. Check the effects too, through the other resources: the row in the database, the message on Kafka.

Reset. Before the next scenario the client forgets its headers and cookies, so no scenario leans on another's.

The suite

app:
  command: node app.js
  port: 3000

resources:
  todos-client: { type: http, base_url: http://localhost:3000 }
Scenario: A todo is added
  When "todos-client" sends "POST" to "/api/todos" with json:
    """
    {"title": "Buy milk"}
    """
  Then "todos-client" response status is "201"
  And "todos-client" response json contains:
    """
    {"title": "Buy milk", "done": false}
    """
  And "todos-client" response json "id" saved as "{{id}}"
  When "todos-client" sends "GET" to "/api/todos/{{id}}"
  Then "todos-client" response json "title" is "Buy milk"

Scenario: A todo needs a title
  When "todos-client" sends "POST" to "/api/todos" with json:
    """
    {"title": "  "}
    """
  Then "todos-client" response status is "400"
  And "todos-client" response json "error" is "title is required"

What it can do

ToStep
set up the requestheader "Authorization" is "Bearer …", query param "page" is "2", cookie "session" is "…"
sendsends "GET" to "/path", … with json:, … with body:
check the statusresponse status is "201", or is client error
check JSONresponse json "a.b[0]" is "x", response json contains: (a subset), response json matches: (all of it)
match loosely"@notempty", "@regex:^ch_", "@gt:10" inside the JSON, or response json "id" is uuid
carry a value forwardresponse json "id" saved as "{{id}}", then use {{id}} in any later step
read a streamopens stream "/events", receives event "tick" within "2s" (Server-Sent Events)
load itsends "20" concurrent "GET" to "/path", "20" of the concurrent responses have status "200"

Notes

  • contains is a subset, matches is exact. Use contains for the fields the scenario is about, so a new field doesn’t break every scenario.
  • Headers set with header stay for the rest of the scenario, and are cleared before the next.
  • Point it at anything: another service in a container with container:, or the address of a service tomato doesn’t start.