DEV Community

Richard Hatherall
Richard Hatherall

Posted on • Updated on • Originally published at appercept.com

Matching HTTP requests with WebMocks in Delphi

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:

// 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');
Enter fullscreen mode Exit fullscreen mode

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:

WebMock.StubRequest('*', '/widgets/1');
Enter fullscreen mode Exit fullscreen mode

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.

WebMock.StubRequest('GET', '/widgets/1');
WebMock.StubRequest('DELETE', '/widgets/1');
WebMock.StubRequest('*', '/widgets/1'); // PATCH and PUT
Enter fullscreen mode Exit fullscreen mode

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:

// Get Widget with an integer ID
WebMock.StubRequest('GET', TRegEx.Create('/widgets/\d+'));
Enter fullscreen mode Exit fullscreen mode

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:

WebMock.StubRequest('*', '*')
  .WithQueryParam('Action', 'DoSomething');
Enter fullscreen mode Exit fullscreen mode

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

WebMock.StubRequest('*', '*')
  .WithQueryParam('Action', '*');
Enter fullscreen mode Exit fullscreen mode

Using regular expressions is also supported for matching values:

WebMock.StubRequest('*', '*')
  .WithQueryParam('Action', TRegEx.Create('List.*'));
Enter fullscreen mode Exit fullscreen mode

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:

WebMock.StubRequest('*', '*')
  .WithHeader('content-type', 'video/mp4');
Enter fullscreen mode Exit fullscreen mode

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:

WebMock.StubRequest('*', '*')
  .WithHeader('content-type', TRegEx.Create('video/.+'));
Enter fullscreen mode Exit fullscreen mode

If you already have a TStringList populated with headers, the WithHeaders method can be used to match against all values:

var
  LHeaders: TStringList;
begin
  LHeaders := TStringList.Create;
  LHeaders.Values['content-type'] := 'application/json';
  LHeaders.Values['accept'] := 'application/json';

  WebMock.StubRequest('*', '*')
    .WithHeaders(LHeaders);
end;
Enter fullscreen mode Exit fullscreen mode

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:

WebMock.StubRequest('*', '*')
  .WithBody('Hello');
Enter fullscreen mode Exit fullscreen mode

Matching a pattern can be achieved with a regular expression.

WebMock.StubRequest('*', '*')
  .WithBody(TRegEx.Create('Hello'));
Enter fullscreen mode Exit fullscreen mode

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.

WebMock.StubRequest('*', '*')
  .WithFormData('field', 'value');
Enter fullscreen mode Exit fullscreen mode

Or, to match a RegEx pattern:

WebMock.StubRequest('*', '*')
  .WithFormData('field', TRegEx.Create('pattern'));
Enter fullscreen mode Exit fullscreen mode

Matching JSON

JSON data is more complicated as it has structure and typed values. To match a JSON attribute in the example content:

{
  "name": "Chester Copperpot"
}
Enter fullscreen mode Exit fullscreen mode

The following example will provide a positive match:

WebMock.StubRequest('*', '*')
  .WithJSON('name', 'Chester Copperpot');
Enter fullscreen mode Exit fullscreen mode

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:

WebMock.StubRequest('*', '*')
  .WithJSON('users[0].name', 'Chester Copperpot');
Enter fullscreen mode Exit fullscreen mode

Putting it all together

To demonstrate a more comprehensive example:

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);
Enter fullscreen mode Exit fullscreen mode

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.

Top comments (0)