Matching HTTP requests with WebMocks in Delphi
Tags: delphi, testing, tdd, webmocks
Published: 2021-03-17 11:26:22

If you've been following along, you'll now know how to set up a DUnitX project with WebMocks and understand how to define the HTTP response for a stubbed request. You've also seen the most basic form of request matching in the previous articles, e.g. `StubRequest('GET', '/')`. The `StubRequest` method starts with the required elements of a request match, the HTTP method `GET` in this example, along with the document path `/`. Let's take a closer look at how we can match requests with WebMocks in Delphi.

# Methods and Document Paths
The HTTP method is one of the most significant components of the request. Therefore, the `StubRequest` method puts that upfront, along with the intended document path. They are both specified as `string` arguments, allowing arbitrary method and path values. RESTful endpoints can be matched as follows:
```Pascal
// Create Widget
WebMock.StubRequest('POST', '/widgets');

// List all Widgets
WebMock.StubRequest('GET', '/widgets');

// Show Widget 1
WebMock.StubRequest('GET', '/widgets/1');

// Update Widget 1
WebMock.StubRequest('PATCH', '/widgets/1');
WebMock.StubRequest('PUT', '/widgets/1');

// Delete Widget 1
WebMock.StubRequest('DELETE', '/widgets/1');
```

As you can see from these examples, there are often many similar requests that you will need to distinguish correctly based upon method and path alone.

Sometimes it is useful to catch more than a single value, and you might, for example, want to stub the update requests represented by `PATCH` and `PUT` with a single call. A simple wildcard "*" string matches any value:
```Pascal
WebMock.StubRequest('*', '/widgets/1');
```

Now, the eagle-eyed amongst you will be saying, "won't that match the GET and DELETE actions too?". Yes, it will. Most test scenarios don't need all of the actions defined simultaneously. If you need to, then it is easily achieved through the order of stub definitions. All request stubs are evaluated in the order they are defined, and the first matching stub is applied.
```Pascal
WebMock.StubRequest('GET', '/widgets/1');
WebMock.StubRequest('DELETE', '/widgets/1');
WebMock.StubRequest('*', '/widgets/1'); // PATCH and PUT
```

Another potential problem here is the ID in the URL. You might not know the exact number when writing the test, or you might want to match and respond the same to multiple identical requests for different resources. Unfortunately, the `*` is a simple wildcard only, and you can't combine it with a partial path. Luckily, WebMocks does allow regular-expressions in place of the path argument:
```Pascal
// Get Widget with an integer ID
WebMock.StubRequest('GET', TRegEx.Create('/widgets/\d+'));
```

Be sure to include `System.RegularExpressions` in your `uses` clause.

# Matching Query Parameters
The document path is only one part of the URI. It is just as essential to be able to match by query parameters. Query parameters can be matched by name and value easily. For example, to match a specific value for a parameter supplied on the URL query:
```Pascal
WebMock.StubRequest('*', '*')
  .WithQueryParam('Action', 'DoSomething');
```

If all you want is to test for the presence of a particular query parameter, you can use a simple wildcard `*`:
```Pascal
WebMock.StubRequest('*', '*')
  .WithQueryParam('Action', '*');
```

Using regular expressions is also supported for matching values:
```Pascal
WebMock.StubRequest('*', '*')
  .WithQueryParam('Action', TRegEx.Create('List.*'));
```

# Matching Headers
Headers provide a lot of information about a request, and you are likely going to want to match a request by header values.
Let's start with a basic example:
```Pascal
WebMock.StubRequest('*', '*')
  .WithHeader('content-type', 'video/mp4');
```

This example will match any request with a `content-type` header of `video/mp4`. Like the HTTP method and document path, the header value can be a simple wildcard `*`. The wildcard is useful if you want to match against the presence of the header. Again, like the document path, the header value can be specified as a regular expression. The following example is useful for matching any video content:
```Pascal
WebMock.StubRequest('*', '*')
  .WithHeader('content-type', TRegEx.Create('video/.+'));
```

If you already have a `TStringList` populated with headers, the `WithHeaders` method can be used to match against all values:
```Pascal
var
  LHeaders: TStringList;
begin
  LHeaders := TStringList.Create;
  LHeaders.Values['content-type'] := 'application/json';
  LHeaders.Values['accept'] := 'application/json';

  WebMock.StubRequest('*', '*')
    .WithHeaders(LHeaders);
end;
```

# Matching Body Content
Matching by body content is where it can get a little more interesting. You can probably guess that we have a method called `WithBody` that does pretty much exactly what you think:
```Pascal
WebMock.StubRequest('*', '*')
  .WithBody('Hello');
```

Matching a pattern can be achieved with a regular expression.
```Pascal
WebMock.StubRequest('*', '*')
  .WithBody(TRegEx.Create('Hello'));
```

Matching an exact string or pattern is all well and good, but when testing APIs, you're most likely dealing with some form of structured data. WebMocks provides several methods for dealing with two standard formats: form-data; and JSON.

## Matching Form Data
The `WithFormData` method matches form-data values as submitted with the content-type `application/x-www-form-urlencoded`.
```Pascal
WebMock.StubRequest('*', '*')
  .WithFormData('field', 'value');
```

Or, to match a RegEx pattern:
```Pascal
WebMock.StubRequest('*', '*')
  .WithFormData('field', TRegEx.Create('pattern'));
```

## Matching JSON
JSON data is more complicated as it has structure and typed values. To match a JSON attribute in the example content:
```JSON
{
  "name": "Chester Copperpot"
}
```

The following example will provide a positive match:
```Pascal
WebMock.StubRequest('*', '*')
  .WithJSON('name', 'Chester Copperpot');
```

Overloaded versions of the `WithJSON` method are defined to handle the data types in JSON: string, number (integer and decimal), and boolean. The JSON types of array and object are structural, and comparing them is not possible but navigating them is.

If you have a deeply nested JSON structure that you want to match values against you can provide the first argument as [JSONPath](https://goessner.net/articles/JsonPath/). For example:
```Pascal
WebMock.StubRequest('*', '*')
  .WithJSON('users[0].name', 'Chester Copperpot');
```

# Putting it all together
To demonstrate a more comprehensive example:
```Pascal
WebMock.StubRequest('POST', '/register')
  .WithQueryParam('source', 'http://example.com')
  .WithHeader('content-type', 'text/json')
  .WithJSON('user.email', 'ccopperpot@example.com')
  .WithJSON('user.password', 'goonie4ever')
  .WithJSON('user.name', 'Chester Copperpot')
  .ToRespond
  .WithStatus(Created);
```
This one example demonstrates the use of matching all HTTP message parts. While WebMocks provides an elegant and flexible interface for this, remember what the goal here is: identifying a request and responding appropriately to test some behaviour. To that end, maybe matching the document and method is enough?

If you want to verify the request's format, you should look at "assertions". Luckily I have an upcoming article all about that "Asserting HTTP requests with WebMocks in Delphi". In the meantime, try [WebMocks](https://github.com/appercept/Delphi-WebMocks) and [Delphi](https://www.embarcadero.com/products/delphi) for yourself. The demos from this article and more are available on [GitHub](https://github.com/appercept/Delphi-WebMocks-Demos). Let me know how you get on in the comments below or on [Twitter](https://twitter.com/RichHatherall).