Skip to content

Commit 092b12b

Browse files
committed
Merge branch 'release/3.0.0'
2 parents 23b428b + 0164ad6 commit 092b12b

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

44 files changed

+3390
-518
lines changed

.gitignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,5 +67,6 @@ __recovery/
6767

6868
# Ignore Build outputs
6969
Build
70+
TestInsightSettings.ini
7071
Tests/Win32
71-
Tests/Win64
72+
Tests/Win64

CHANGELOG.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,20 @@
11
# Changelog
22

3+
## [3.0.0](https://github.com/appercept/Delphi-WebMocks/tree/3.0.0) (2021-01-27)
4+
5+
[Full Changelog](https://github.com/appercept/Delphi-WebMocks/compare/2.0.0...3.0.0)
6+
7+
**Implemented enhancements:**
8+
9+
- Assertion matching by URL query parameters [\#41](https://github.com/appercept/Delphi-WebMocks/issues/41)
10+
- Request content matching by Form-Data [\#20](https://github.com/appercept/Delphi-WebMocks/issues/20)
11+
- Request matching by JSON values
12+
13+
**Fixed bugs:**
14+
15+
- Test failure due to platform line endings [\#43](https://github.com/appercept/Delphi-WebMocks/issues/43)
16+
- Request history is not persisting query parameters [\#42](https://github.com/appercept/Delphi-WebMocks/issues/42)
17+
318
## [2.0.0](https://github.com/appercept/Delphi-WebMocks/tree/2.0.0) (2020-11-25)
419

520
[Full Changelog](https://github.com/appercept/Delphi-WebMocks/compare/1.3.0...2.0.0)

README.md

Lines changed: 66 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
![Delphi compatibility](https://img.shields.io/badge/Delphi-XE8%20or%20newer-brightgreen)
1+
![Delphi compatibility](https://img.shields.io/badge/Delphi%20compatability-10.3%20or%20newer-brightgreen)
22
![Platform compatibility](https://img.shields.io/badge/platform-Linux64%20%7C%20macOS64%20%7C%20Win32%20%7C%20Win64-lightgrey)
33
![License](https://img.shields.io/github/license/appercept/Delphi-WebMocks)
44
![Lines of Code](https://tokei.rs/b1/github/appercept/Delphi-WebMocks)
@@ -8,14 +8,22 @@ Library for stubbing and setting expectations on HTTP requests in Delphi with
88
[DUnitX](https://github.com/VSoftTechnologies/DUnitX).
99

1010
## Requirements
11-
* [Delphi](https://www.embarcadero.com/products/delphi) XE8 or later*
11+
* [Delphi](https://www.embarcadero.com/products/delphi) 10.3 (Rio) or later*
1212
* [DUnitX](https://github.com/VSoftTechnologies/DUnitX)
1313
* [Indy](https://www.indyproject.org)
1414

15-
\* WebMocks was developed in Delphi 10.3 (Rio) and 10.4 (Sydney). WebMocks has
16-
been reported working on 10.1 (Berlin). I'd be interested to hear from anyone
17-
working on other versions. As WebMocks makes use of the `System.Net`
18-
library introduced with XE8 it will not be compatible with earlier versions.
15+
\* WebMocks was developed in Delphi 10.3 (Rio) and 10.4 (Sydney) and until
16+
version 3.0 was compatible back to XE8. As WebMocks makes use of the
17+
`System.Net` library introduced with XE8 it will not be compatible with earlier
18+
versions. Should you require installing on Delphi versions prior to 10.3 you
19+
should install version
20+
[2.0.0](https://github.com/appercept/Delphi-WebMocks/releases/tag/2.0.0).
21+
22+
## Installation: GetIt
23+
[WebMocks 2.0](https://getitnow.embarcadero.com/WebMocks-2.0/) is available
24+
through Embarcadero's package manager for Delphi
25+
[GetIt](https://getitnow.embarcadero.com/). If you have a recent version of
26+
Delphi including GetIt then this should be the preferred installation method.
1927

2028
## Installation: Delphinus-Support
2129
WebMocks should now be listed in
@@ -26,11 +34,11 @@ not be found in your test projects.
2634

2735
## Installation: Manual
2836
1. Download and extract the latest version
29-
[2.0.0](https://github.com/appercept/Delphi-WebMocks/archive/2.0.0.zip).
37+
[3.0.0](https://github.com/appercept/Delphi-WebMocks/archive/3.0.0.zip).
3038
2. In "Tools > Options" under the "Language / Delphi / Library" add the
3139
extracted `Source` directory to the "Library path" and "Browsing path".
3240

33-
## Upgrading to versions prior to 2.0.0
41+
## Upgrading from versions prior to 2.0.0
3442
Version 2 has dropped the `Delphi.` namespace from all units. Any projects
3543
upgrade to version 2 or later will need to drop the `Delphi.` prefix from any
3644
included WebMocks units.
@@ -169,6 +177,23 @@ HTTP request can be matched by content like:
169177
WebMock.StubRequest('*', '*').WithBody('String content.');
170178
```
171179

180+
#### Request matching by form-data
181+
HTTP requests can be matched by form-data as submitted with `content-type` of
182+
`application/x-www-form-urlencoded`. Multiple matching field values can be
183+
combined. For example:
184+
```Delphi
185+
WebMock.StubRequest('*', '*')
186+
.WithFormData('AField', 'A value.')
187+
.WithFormData('AOtherField', 'Another value.');
188+
```
189+
190+
To simply match the presence of a field, a wildcard `*` can be passed for the
191+
value.
192+
193+
NOTE: You cannot match form-data (`WithFormData`) and body content (`WithBody`)
194+
at the same time. Specifying both will result in the latest call overwriting the
195+
previous matcher.
196+
172197
#### Matching request document path, headers, or content by regular-expression
173198
Matching a request by regular-expression can be useful for stubbing dynamic
174199
routes for a ReSTful resource involving a resource name and an unknown resource
@@ -189,8 +214,37 @@ WebMock.StubRequest('*', '*')
189214
.WithBody(TRegEx.Create('Hello'));
190215
```
191216

217+
Matching form-data content can be performed like:
218+
```Delphi
219+
WebMock.StubRequest('*', '*')
220+
.WithFormData('AField', TRegEx.Create('.*'));
221+
```
222+
192223
NOTE: Be sure to add `System.RegularExpressions` to your uses clause.
193224

225+
#### Request matching by JSON
226+
HTTP requests can be matched by JSON data as submitted with `content-type` of
227+
`application/json` using `WithJSON`. Multiple matching field values can be
228+
combined. For example:
229+
```Delphi
230+
WebMock.StubRequest('*', '*')
231+
.WithJSON('ABoolean', True)
232+
.WithJSON('AFloat', 0.123)
233+
.WithJSON('AInteger', 1)
234+
.WithJSON('AString', 'value');
235+
```
236+
237+
The first argument can be a path. For example, in the following JSON, the path
238+
`objects[0].key` would match `value 1`.
239+
```JSON
240+
{
241+
"objects": [
242+
{ "key": "value 1" },
243+
{ "key": "value 2" }
244+
]
245+
}
246+
```
247+
194248
#### Request matching by predicate function
195249
If matching logic is required to be more complex than the simple matching, a
196250
predicate function can be provided in the test to allow custom inspection/logic
@@ -373,11 +427,12 @@ WebClient.Get(WebMock.URLFor('/'));
373427
WebMock.Assert.Get('/').WasRequested; // Passes
374428
```
375429

376-
As with request stubbing you can match requests by HTTP Method, URI, Headers,
377-
and Body content.
430+
As with request stubbing you can match requests by HTTP Method, URI, Query
431+
Parameters, Headers, and Body content (including `WithJSON`).
378432
```Delphi
379433
WebMock.Assert
380434
.Patch('/resource`)
435+
.WithQueryParam('ParamName', 'Value')
381436
.WithHeader('Content-Type', 'application/json')
382437
.WithBody('{ "resource": { "propertyA": "Value" } }')
383438
.WasRequested;
@@ -398,7 +453,7 @@ performing extra unwanted requests.
398453
This project follows [Semantic Versioning](https://semver.org).
399454

400455
## License
401-
Copyright ©2019-2020 Richard Hatherall <[email protected]>
456+
Copyright ©2019-2021 Richard Hatherall <[email protected]>
402457

403458
WebMocks is distributed under the terms of the Apache License (Version 2.0).
404459

Source/WebMock.Assertion.pas

Lines changed: 111 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
{ }
33
{ Delphi-WebMocks }
44
{ }
5-
{ Copyright (c) 2019 Richard Hatherall }
5+
{ Copyright (c) 2019-2021 Richard Hatherall }
66
{ }
77
88
{ https://appercept.com }
@@ -29,17 +29,20 @@ interface
2929

3030
uses
3131
DUnitX.TestFramework,
32-
System.Classes, System.Generics.Collections, System.RegularExpressions,
33-
WebMock.HTTP.Messages, WebMock.HTTP.RequestMatcher;
32+
System.Classes,
33+
System.RegularExpressions,
34+
System.Rtti,
35+
WebMock.HTTP.Messages,
36+
WebMock.HTTP.RequestMatcher;
3437

3538
type
3639
TWebMockAssertion = class(TObject)
3740
private
38-
FMatcher: TWebMockHTTPRequestMatcher;
39-
FHistory: TList<IWebMockHTTPRequest>;
41+
FMatcher: IWebMockHTTPRequestMatcher;
42+
FHistory: IInterfaceList;
4043
function MatchesHistory: Boolean;
4144
public
42-
constructor Create(AHistory: TList<IWebMockHTTPRequest>);
45+
constructor Create(const AHistory: IInterfaceList);
4346
function Delete(const AURI: string): TWebMockAssertion;
4447
function Get(const AURI: string): TWebMockAssertion;
4548
function Patch(const AURI: string): TWebMockAssertion;
@@ -52,10 +55,18 @@ TWebMockAssertion = class(TObject)
5255
function WithHeader(const AName, AValue: string): TWebMockAssertion; overload;
5356
function WithHeader(const AName: string; const APattern: TRegEx): TWebMockAssertion; overload;
5457
function WithHeaders(const AHeaders: TStringList): TWebMockAssertion;
58+
function WithQueryParam(const AName, AValue: string): TWebMockAssertion; overload;
59+
function WithQueryParam(const AName: string; const APattern: TRegEx): TWebMockAssertion; overload;
60+
function WithFormData(const AName, AValue: string): TWebMockAssertion; overload;
61+
function WithFormData(const AName: string; const APattern: TRegEx): TWebMockAssertion; overload;
62+
function WithJSON(const APath: string; AValue: Boolean): TWebMockAssertion; overload;
63+
function WithJSON(const APath: string; AValue: Float64): TWebMockAssertion; overload;
64+
function WithJSON(const APath: string; AValue: Integer): TWebMockAssertion; overload;
65+
function WithJSON(const APath: string; AValue: string): TWebMockAssertion; overload;
5566
procedure WasRequested;
5667
procedure WasNotRequested;
57-
property History: TList<IWebMockHTTPRequest> read FHistory;
58-
property Matcher: TWebMockHTTPRequestMatcher read FMatcher;
68+
property History: IInterfaceList read FHistory;
69+
property Matcher: IWebMockHTTPRequestMatcher read FMatcher;
5970
end;
6071

6172
implementation
@@ -64,9 +75,11 @@ implementation
6475

6576
uses
6677
System.SysUtils,
67-
WebMock.StringRegExMatcher, WebMock.StringWildcardMatcher;
78+
WebMock.FormDataMatcher,
79+
WebMock.StringRegExMatcher,
80+
WebMock.StringWildcardMatcher;
6881

69-
constructor TWebMockAssertion.Create(AHistory: TList<IWebMockHTTPRequest>);
82+
constructor TWebMockAssertion.Create(const AHistory: IInterfaceList);
7083
begin
7184
inherited Create;
7285
FHistory := AHistory;
@@ -84,12 +97,15 @@ function TWebMockAssertion.Get(const AURI: string): TWebMockAssertion;
8497

8598
function TWebMockAssertion.MatchesHistory: Boolean;
8699
var
100+
I: Integer;
87101
LRequest: IWebMockHTTPRequest;
88102
begin
89103
Result := False;
90104

91-
for LRequest in History do
105+
for I := 0 to History.Count - 1 do
92106
begin
107+
LRequest := History[I] as IWebMockHTTPRequest;
108+
93109
if Matcher.IsMatch(LRequest) then
94110
begin
95111
Result := True;
@@ -130,51 +146,69 @@ function TWebMockAssertion.Request(const AMethod, AURI: string): TWebMockAsserti
130146

131147
procedure TWebMockAssertion.WasNotRequested;
132148
begin
133-
if MatchesHistory then
134-
Assert.Fail(Format('Found request matching %s', [Matcher.ToString]))
135-
else
136-
Assert.Pass(Format('Did not find request matching %s', [Matcher.ToString]));
149+
try
150+
if MatchesHistory then
151+
Assert.Fail(Format('Found request matching %s', [Matcher.ToString]))
152+
else
153+
Assert.Pass(Format('Did not find request matching %s', [Matcher.ToString]));
154+
finally
155+
Free;
156+
end;
137157
end;
138158

139159
procedure TWebMockAssertion.WasRequested;
140160
begin
141-
if MatchesHistory then
142-
Assert.Pass(Format('Found request matching %s', [Matcher.ToString]))
143-
else
144-
Assert.Fail(Format('Expected to find request matching %s', [Matcher.ToString]));
161+
try
162+
if MatchesHistory then
163+
Assert.Pass(Format('Found request matching %s', [Matcher.ToString]))
164+
else
165+
Assert.Fail(Format('Expected to find request matching %s', [Matcher.ToString]));
166+
finally
167+
Free;
168+
end;
145169
end;
146170

147171
function TWebMockAssertion.WithBody(const APattern: TRegEx): TWebMockAssertion;
148172
begin
149-
Matcher.Body := TWebMockStringRegExMatcher.Create(APattern);
173+
Matcher.Builder.WithBody(APattern);
174+
175+
Result := Self;
176+
end;
177+
178+
function TWebMockAssertion.WithFormData(const AName: string;
179+
const APattern: TRegEx): TWebMockAssertion;
180+
begin
181+
Matcher.Builder.WithFormData(AName, APattern);
182+
183+
Result := Self;
184+
end;
185+
186+
function TWebMockAssertion.WithFormData(const AName,
187+
AValue: string): TWebMockAssertion;
188+
begin
189+
Matcher.Builder.WithFormData(AName, AValue);
150190

151191
Result := Self;
152192
end;
153193

154194
function TWebMockAssertion.WithHeader(const AName,
155195
AValue: string): TWebMockAssertion;
156196
begin
157-
Matcher.Headers.AddOrSetValue(
158-
AName,
159-
TWebMockStringWildcardMatcher.Create(AValue)
160-
);
197+
Matcher.Builder.WithHeader(AName, AValue);
161198

162199
Result := Self;
163200
end;
164201

165202
function TWebMockAssertion.WithBody(const AContent: string): TWebMockAssertion;
166203
begin
167-
Matcher.Body := TWebMockStringWildcardMatcher.Create(AContent);
204+
Matcher.Builder.WithBody(AContent);
168205

169206
Result := Self;
170207
end;
171208

172209
function TWebMockAssertion.WithHeader(const AName: string; const APattern: TRegEx): TWebMockAssertion;
173210
begin
174-
Matcher.Headers.AddOrSetValue(
175-
AName,
176-
TWebMockStringRegExMatcher.Create(APattern)
177-
);
211+
Matcher.Builder.WithHeader(AName, APattern);
178212

179213
Result := Self;
180214
end;
@@ -190,4 +224,52 @@ function TWebMockAssertion.WithHeaders(
190224
Result := Self;
191225
end;
192226

227+
function TWebMockAssertion.WithJSON(const APath: string;
228+
AValue: Boolean): TWebMockAssertion;
229+
begin
230+
Matcher.Builder.WithJSON(APath, AValue);
231+
232+
Result := Self;
233+
end;
234+
235+
function TWebMockAssertion.WithJSON(const APath: string;
236+
AValue: Float64): TWebMockAssertion;
237+
begin
238+
Matcher.Builder.WithJSON(APath, AValue);
239+
240+
Result := Self;
241+
end;
242+
243+
function TWebMockAssertion.WithJSON(const APath: string;
244+
AValue: Integer): TWebMockAssertion;
245+
begin
246+
Matcher.Builder.WithJSON(APath, AValue);
247+
248+
Result := Self;
249+
end;
250+
251+
function TWebMockAssertion.WithJSON(const APath: string;
252+
AValue: string): TWebMockAssertion;
253+
begin
254+
Matcher.Builder.WithJSON(APath, AValue);
255+
256+
Result := Self;
257+
end;
258+
259+
function TWebMockAssertion.WithQueryParam(const AName: string;
260+
const APattern: TRegEx): TWebMockAssertion;
261+
begin
262+
Matcher.Builder.WithQueryParam(AName, APattern);
263+
264+
Result := Self;
265+
end;
266+
267+
function TWebMockAssertion.WithQueryParam(const AName,
268+
AValue: string): TWebMockAssertion;
269+
begin
270+
Matcher.Builder.WithQueryParam(AName, AValue);
271+
272+
Result := Self;
273+
end;
274+
193275
end.

0 commit comments

Comments
 (0)