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

Driving your service over gRPC

clientsgrpc
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.

What it needs: reflection

The client asks your service for its own schema, through gRPC server reflection, so you name a method the way you’d say it, and write messages as JSON. One line in the service turns it on:

reflection.Register(server)                          // Go
new ReflectionService(packageDefinition).addToServer(server)   // Node.js, @grpc/reflection

The suite

resources:
  todos-client: { type: grpc, address: localhost:50051 }
Scenario: The service describes itself
  Then "todos-client" exposes service "todos.v1.TodoService"
  And "todos-client" service "todos.v1.TodoService" has method "AddTodo"

Scenario: A todo is added
  When "todos-client" calls "todos.v1.TodoService/AddTodo" with:
    """
    {"title": "Buy milk"}
    """
  Then "todos-client" call succeeds
  And "todos-client" response json "title" is "Buy milk"
  And "todos-client" response json "done" is "false"

Scenario: A todo needs a title
  When "todos-client" calls "todos.v1.TodoService/AddTodo" with:
    """
    {"title": ""}
    """
  Then "todos-client" call fails
  And "todos-client" response status is "INVALID_ARGUMENT"
  And "todos-client" response error contains "title is required"

What it can do

ToStep
send metadatametadata "authorization" is "Bearer …"
callcalls "pkg.Service/Method", … with: a JSON message
check the outcomecall succeeds, call fails, response status is "NOT_FOUND", response error contains "…"
check the messageevery JSON check the http client has: response json "a.b" is, contains:, matches:
check the schemaexposes service "…", service "…" has method "…"

Notes

  • A failed call is a result, not a broken step. INVALID_ARGUMENT is something to assert. Only a call that can’t be made (an unknown method, a message that doesn’t fit) fails the step.
  • Zero values are in the JSON. "done": false is there to assert, though protobuf doesn’t send it.
  • Enums are names, int64 is a string. "ROLE_ADMIN", "priceCents": "250".
  • Unary calls only. Streaming calls need steps of their own; they’re refused with a clear message for now.