Skip to main content

Building requests

Proto.Context.Rest(name) returns a RestRequestBuilder. Configure it fluently, then finish with a verb method.

using var response = await Proto.Context.Rest()
.Header("X-Correlation-Id", correlationId)
.Body(new CreateOrderRequest("observability-seat", 12, 19.95m))
.PostAsync("/api/orders");

Verbs​

VerbSends
GetAsync(route, params?, ct?)GET
PostAsync(route, params?, ct?)POST
PutAsync(route, params?, ct?)PUT
PatchAsync(route, params?, ct?)PATCH
DeleteAsync(route, params?, ct?)DELETE
HeadAsync(route, params?, ct?)HEAD
OptionsAsync(route, params?, ct?)OPTIONS
SendAsync(method, route, params?, ct?)any HttpMethod, for verbs with no helper

Each returns Task<RestResponse> and takes the same route template and optional route-and-query object.

RestResponse is IDisposable; use using var (the response body is already buffered, so disposing doesn't cut anything short).

Route templates and parameters​

The second argument fills {placeholders} in the route; whatever is left over becomes the query string.

await Proto.Context.Rest().GetAsync(
"/api/workspaces/{workspaceId}/releases",
new { workspaceId = workspace.Id, state = "deployed", page = 2 });

// → /api/workspaces/42/releases?state=deployed&page=2

The rules:

InputBecomes
{placeholder} with a matching valuea path segment, matched case-insensitively ({workspaceId}=42 → /workspaces/42)
{placeholder} with no value, or a null oneArgumentException naming the parameter
leftover non-null propertya query parameter (state="deployed" → ?state=deployed)
null property, or an empty collectiondropped (nothing emitted)
collection valuea repeated key (tags=[a,b] → tags=a&tags=b)
bool, date or time valueinvariant text (true, round-trip "O" dates)
#fragment in the templatepreserved, re-appended after the query string
absolute URL as the templateused as-is; the client's base address is bypassed
non-HTTP(S) scheme, or a relative route with no base addressInvalidOperationException
colon in the first segment (orders:search)a relative path, as RFC 3986 requires

Keys and values are escaped with Uri.EscapeDataString. A dictionary with string keys works in place of the object.

A per-test base-address resolver wins over the client's HttpClient.BaseAddress at request time.

Bodies​

RestRequestBuilder Body(object payload, JsonSerializerOptions? options = null); // JSON
RestRequestBuilder Body(string rawContent, string mediaType = "text/plain");
RestRequestBuilder Body(ReadOnlyMemory<byte> content, string mediaType = "application/octet-stream");
RestRequestBuilder Body(Func<HttpContent> contentFactory);

The object overload serialises as application/json. The factory overload is invoked per send, so a re-sent request gets fresh content.

Headers​

RestRequestBuilder Header(string name, string value);

Header names are case-insensitive and the last value for a name wins. ProtoTest tries the request headers first and falls back to the content headers, throwing InvalidOperationException if neither accepts it. The trace records header names and the count, not header values.

Authentication​

RestRequestBuilder Auth(IProtoHttpAuthenticator authenticator);
RestRequestBuilder Auth<TAuthenticator>(params object[] constructorArgs);
RestRequestBuilder WithoutAuth();

See Authentication for how these interact with [Auth<T>].

Response size limit​

Responses are buffered with a cap of 10 MiB by default. A known Content-Length above the cap fails before the body is read; otherwise the buffer stops mid-read. Either way the failure is ProtoResponseTooLargeException, whose MaximumBytes and ObservedBytes tell you the configured cap and the observed size. HEAD, 204, 304 and 1xx responses never carry a body, so their headers are not measured against the cap. Change it in code or configuration:

builder.AddApplication("Api", app => app.AddRest(rest => rest
.ConfigureResponses(options => options.MaxResponseBodyBytes = 32 * 1024 * 1024)
.AddClient("Api")));
{ "ProtoTest": { "Rest": { "Responses": { "MaxResponseBodyBytes": 33554432 } } } }

MaxDiagnosticBodyLength (64 KiB) bounds the response body embedded in a status-assertion failure message and in captured attachments. See Attachments for the capture options.