Appercept AWS SDK for DelphiBlog
Get in touch
All posts
Cover image

Matching HTTP requests with WebMocks in Delphi

Richard Hatherall


5 min read

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:

1// Create Widget
2WebMock.StubRequest('POST', '/widgets');
4// List all Widgets
5WebMock.StubRequest('GET', '/widgets');
7// Show Widget 1
8WebMock.StubRequest('GET', '/widgets/1');
10// Update Widget 1
11WebMock.StubRequest('PATCH', '/widgets/1');
12WebMock.StubRequest('PUT', '/widgets/1');
14// Delete Widget 1
15WebMock.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:

1WebMock.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.

1WebMock.StubRequest('GET', '/widgets/1');
2WebMock.StubRequest('DELETE', '/widgets/1');
3WebMock.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:

1// Get Widget with an integer ID
2WebMock.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:

1WebMock.StubRequest('*', '*')
2 .WithQueryParam('Action', 'DoSomething');

If all you want is to test for the presence of a particular query parameter, you can use a simple wildcard *:

1WebMock.StubRequest('*', '*')
2 .WithQueryParam('Action', '*');

Using regular expressions is also supported for matching values:

1WebMock.StubRequest('*', '*')
2 .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:

1WebMock.StubRequest('*', '*')
2 .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:

1WebMock.StubRequest('*', '*')
2 .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:

2 LHeaders: TStringList;
4 LHeaders := TStringList.Create;
5 LHeaders.Values['content-type'] := 'application/json';
6 LHeaders.Values['accept'] := 'application/json';
8 WebMock.StubRequest('*', '*')
9 .WithHeaders(LHeaders);

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:

1WebMock.StubRequest('*', '*')
2 .WithBody('Hello');

Matching a pattern can be achieved with a regular expression.

1WebMock.StubRequest('*', '*')
2 .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.

1WebMock.StubRequest('*', '*')
2 .WithFormData('field', 'value');

Or, to match a RegEx pattern:

1WebMock.StubRequest('*', '*')
2 .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:

2 "name": "Chester Copperpot"

The following example will provide a positive match:

1WebMock.StubRequest('*', '*')
2 .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. For example:

1WebMock.StubRequest('*', '*')
2 .WithJSON('users[0].name', 'Chester Copperpot');

Putting it all together


To demonstrate a more comprehensive example:

1WebMock.StubRequest('POST', '/register')
2 .WithQueryParam('source', '')
3 .WithHeader('content-type', 'text/json')
4 .WithJSON('', '')
5 .WithJSON('user.password', 'goonie4ever')
6 .WithJSON('', 'Chester Copperpot')
7 .ToRespond
8 .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 and Delphi for yourself. The demos from this article and more are available on GitHub. Let me know how you get on in the comments below or on Twitter.

© 2020–2022 Appercept Ltd. All rights reserved.