Skip to content

Commit 3c5360d

Browse files
authored
Enable different responses on consecutive calls (#64)
1 parent 7fff200 commit 3c5360d

File tree

8 files changed

+220
-14
lines changed

8 files changed

+220
-14
lines changed

doc/stubbing.md

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,3 +107,32 @@ $this->http->mock
107107

108108
By using `once()`, the second request will lead to a 404 HTTP Not Found, as if the request would have been undefined.
109109
Other methods to limit validity are `once()`, `twice()`, `thrice()` and `exactly(int $count)`.
110+
111+
## Getting different response on successive identical queries
112+
113+
In the previous section we saw how we could make a stub at most for N queries. But it's also possible to set up different responses on
114+
successive identical queries.
115+
116+
```php
117+
$this->builder
118+
->first()
119+
->when()
120+
->pathIs('/resource')
121+
->methodIs('POST')
122+
->then()
123+
->body('called once');
124+
$this->builder
125+
->second()
126+
->when()
127+
->pathIs('/resource')
128+
->methodIs('POST')
129+
->then()
130+
->body('called twice');
131+
$this->builder
132+
->nth(3)
133+
->when()
134+
->pathIs('/resource')
135+
->methodIs('POST')
136+
->then()
137+
->body('called 3 times');
138+
```

src/Expectation.php

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,17 +24,22 @@ class Expectation
2424
/** @var ExtractorFactory */
2525
private $extractorFactory;
2626

27+
/** @var int */
28+
private $priority;
29+
2730
public function __construct(
2831
MockBuilder $mockBuilder,
2932
MatcherFactory $matcherFactory,
3033
ExtractorFactory $extractorFactory,
31-
Closure $limiter
34+
Closure $limiter,
35+
int $priority
3236
)
3337
{
3438
$this->matcherFactory = $matcherFactory;
3539
$this->responseBuilder = new ResponseBuilder($mockBuilder);
3640
$this->extractorFactory = $extractorFactory;
3741
$this->limiter = $limiter;
42+
$this->priority = $priority;
3843
}
3944

4045
public function pathIs($matcher)
@@ -132,6 +137,11 @@ public function getMatcherClosures()
132137
return $closures;
133138
}
134139

140+
public function getPriority()
141+
{
142+
return $this->priority;
143+
}
144+
135145
public function then()
136146
{
137147
return $this->responseBuilder;

src/MockBuilder.php

Lines changed: 48 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,10 @@
77

88
class MockBuilder
99
{
10+
private const PRIORITY_ANY = 0;
11+
private const PRIORITY_EXACTLY = 10;
12+
private const PRIORITY_NTH = 100;
13+
1014
/** @var Expectation[] */
1115
private $expectations = [];
1216

@@ -19,6 +23,9 @@ class MockBuilder
1923
/** @var ExtractorFactory */
2024
private $extractorFactory;
2125

26+
/** @var int */
27+
private $priority;
28+
2229
public function __construct(MatcherFactory $matcherFactory, ExtractorFactory $extractorFactory)
2330
{
2431
$this->matcherFactory = $matcherFactory;
@@ -46,6 +53,32 @@ public function exactly($times)
4653
$this->limiter = static function ($runs) use ($times) {
4754
return $runs < $times;
4855
};
56+
$this->priority = self::PRIORITY_EXACTLY;
57+
58+
return $this;
59+
}
60+
61+
public function first()
62+
{
63+
return $this->nth(1);
64+
}
65+
66+
public function second()
67+
{
68+
return $this->nth(2);
69+
}
70+
71+
public function third()
72+
{
73+
return $this->nth(3);
74+
}
75+
76+
public function nth($position)
77+
{
78+
$this->limiter = static function ($runs) use ($position) {
79+
return $runs === ($position - 1);
80+
};
81+
$this->priority = $position * self::PRIORITY_NTH;
4982

5083
return $this;
5184
}
@@ -55,14 +88,21 @@ public function any()
5588
$this->limiter = static function () {
5689
return true;
5790
};
91+
$this->priority = self::PRIORITY_ANY;
5892

5993
return $this;
6094
}
6195

6296
/** @return Expectation */
6397
public function when()
6498
{
65-
$this->expectations[] = new Expectation($this, $this->matcherFactory, $this->extractorFactory, $this->limiter);
99+
$this->expectations[] = new Expectation(
100+
$this,
101+
$this->matcherFactory,
102+
$this->extractorFactory,
103+
$this->limiter,
104+
$this->priority
105+
);
66106

67107
$this->any();
68108

@@ -74,6 +114,13 @@ public function flushExpectations()
74114
$expectations = $this->expectations;
75115
$this->expectations = [];
76116

117+
usort(
118+
$expectations,
119+
static function (Expectation $left, Expectation $right): int {
120+
return $left->getPriority() <=> $right->getPriority();
121+
}
122+
);
123+
77124
return $expectations;
78125
}
79126
}

src/Server.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -86,8 +86,8 @@ public function setUp(array $expectations)
8686
'/_expectation',
8787
null,
8888
[
89-
'matcher' => serialize($expectation->getMatcherClosures()),
90-
'limiter' => serialize($expectation->getLimiter()),
89+
'matcher' => serialize($expectation->getMatcherClosures()),
90+
'limiter' => serialize($expectation->getLimiter()),
9191
'response' => serialize($expectation->getResponse()),
9292
]
9393
)->send();

src/app.php

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,10 @@ static function (Request $request) use ($app) {
105105
$app->error(
106106
static function (Exception $e, Request $request, $code, GetResponseForExceptionEvent $event = null) use ($app) {
107107
if ($e instanceof NotFoundHttpException) {
108+
if (method_exists($event, 'allowCustomResponseCode')) {
109+
$event->allowCustomResponseCode();
110+
}
111+
108112
$app['storage']->append(
109113
$request,
110114
'requests',
@@ -128,16 +132,14 @@ static function (Exception $e, Request $request, $code, GetResponseForExceptionE
128132
}
129133
}
130134

131-
if (isset($expectation['limiter']) && !$expectation['limiter']($expectation['runs'])) {
132-
$notFoundResponse = new Response('Expectation no longer applicable', Response::HTTP_GONE);
133-
continue;
134-
}
135+
$applicable = !isset($expectation['limiter']) || $expectation['limiter']($expectation['runs']);
135136

136137
++$expectations[$pos]['runs'];
137138
$app['storage']->store($request, 'expectations', $expectations);
138139

139-
if (method_exists($event, 'allowCustomResponseCode')) {
140-
$event->allowCustomResponseCode();
140+
if (!$applicable) {
141+
$notFoundResponse = new Response('Expectation not met', Response::HTTP_GONE);
142+
continue;
141143
}
142144

143145
return $expectation['response'];

tests/MockBuilderIntegrationTest.php

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,4 +123,122 @@ public function testCreateTwoExpectationsAfterEachOther()
123123
$this->assertSame('POST 1', (string) $this->server->getClient()->post('/post-resource-1')->send()->getBody());
124124
$this->assertSame('POST 2', (string) $this->server->getClient()->post('/post-resource-2')->send()->getBody());
125125
}
126+
127+
public function testCreateSuccessiveExpectationsOnSameWhen()
128+
{
129+
$this->builder
130+
->first()
131+
->when()
132+
->pathIs('/resource')
133+
->methodIs('POST')
134+
->then()
135+
->body('called once');
136+
$this->builder
137+
->second()
138+
->when()
139+
->pathIs('/resource')
140+
->methodIs('POST')
141+
->then()
142+
->body('called twice');
143+
$this->builder
144+
->nth(3)
145+
->when()
146+
->pathIs('/resource')
147+
->methodIs('POST')
148+
->then()
149+
->body('called 3 times');
150+
151+
$this->server->setUp($this->builder->flushExpectations());
152+
153+
$this->assertSame('called once', (string) $this->server->getClient()->post('/resource')->send()->getBody());
154+
$this->assertSame('called twice', (string) $this->server->getClient()->post('/resource')->send()->getBody());
155+
$this->assertSame('called 3 times', (string) $this->server->getClient()->post('/resource')->send()->getBody());
156+
}
157+
158+
public function testCreateSuccessiveExpectationsWithAny()
159+
{
160+
$this->builder
161+
->first()
162+
->when()
163+
->pathIs('/resource')
164+
->methodIs('POST')
165+
->then()
166+
->body('1');
167+
$this->builder
168+
->second()
169+
->when()
170+
->pathIs('/resource')
171+
->methodIs('POST')
172+
->then()
173+
->body('2');
174+
$this->builder
175+
->any()
176+
->when()
177+
->pathIs('/resource')
178+
->methodIs('POST')
179+
->then()
180+
->body('any');
181+
182+
$this->server->setUp($this->builder->flushExpectations());
183+
184+
$this->assertSame('1', (string) $this->server->getClient()->post('/resource')->send()->getBody());
185+
$this->assertSame('2', (string) $this->server->getClient()->post('/resource')->send()->getBody());
186+
$this->assertSame('any', (string) $this->server->getClient()->post('/resource')->send()->getBody());
187+
}
188+
189+
public function testCreateSuccessiveExpectationsInUnexpectedOrder()
190+
{
191+
$this->builder
192+
->second()
193+
->when()
194+
->pathIs('/resource')
195+
->methodIs('POST')
196+
->then()
197+
->body('2');
198+
$this->builder
199+
->first()
200+
->when()
201+
->pathIs('/resource')
202+
->methodIs('POST')
203+
->then()
204+
->body('1');
205+
206+
$this->server->setUp($this->builder->flushExpectations());
207+
208+
$this->assertSame('1', (string) $this->server->getClient()->post('/resource')->send()->getBody());
209+
$this->assertSame('2', (string) $this->server->getClient()->post('/resource')->send()->getBody());
210+
}
211+
212+
public function testCreateSuccessiveExpectationsWithOnce()
213+
{
214+
$this->builder
215+
->first()
216+
->when()
217+
->pathIs('/resource')
218+
->methodIs('POST')
219+
->then()
220+
->body('1');
221+
$this->builder
222+
->second()
223+
->when()
224+
->pathIs('/resource')
225+
->methodIs('POST')
226+
->then()
227+
->body('2');
228+
$this->builder
229+
->twice()
230+
->when()
231+
->pathIs('/resource')
232+
->methodIs('POST')
233+
->then()
234+
->body('twice');
235+
236+
$this->server->setUp($this->builder->flushExpectations());
237+
238+
$this->assertSame('1', (string) $this->server->getClient()->post('/resource')->send()->getBody());
239+
$this->assertSame('2', (string) $this->server->getClient()->post('/resource')->send()->getBody());
240+
$this->assertSame('twice', (string) $this->server->getClient()->post('/resource')->send()->getBody());
241+
$this->assertSame('twice', (string) $this->server->getClient()->post('/resource')->send()->getBody());
242+
$this->assertSame('Expectation not met', (string) $this->server->getClient()->post('/resource')->send()->getBody());
243+
}
126244
}

tests/PHPUnit/HttpMockMultiPHPUnitIntegrationTest.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -140,7 +140,7 @@ public function testLimitDurationOfAResponse()
140140
$this->assertSame(200, $firstResponse->getStatusCode());
141141
$secondResponse = $this->http['firstNamedServer']->client->post('/')->send();
142142
$this->assertSame(410, $secondResponse->getStatusCode());
143-
$this->assertSame('Expectation no longer applicable', $secondResponse->getBody(true));
143+
$this->assertSame('Expectation not met', $secondResponse->getBody(true));
144144

145145
$this->http['firstNamedServer']->mock
146146
->exactly(2)
@@ -156,7 +156,7 @@ public function testLimitDurationOfAResponse()
156156
$this->assertSame(200, $secondResponse->getStatusCode());
157157
$thirdResponse = $this->http['firstNamedServer']->client->post('/')->send();
158158
$this->assertSame(410, $thirdResponse->getStatusCode());
159-
$this->assertSame('Expectation no longer applicable', $thirdResponse->getBody(true));
159+
$this->assertSame('Expectation not met', $thirdResponse->getBody(true));
160160

161161
$this->http['firstNamedServer']->mock
162162
->any()

tests/PHPUnit/HttpMockPHPUnitIntegrationTest.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -140,7 +140,7 @@ public function testLimitDurationOfAResponse()
140140
$this->assertSame(200, $firstResponse->getStatusCode());
141141
$secondResponse = $this->http->client->post('/')->send();
142142
$this->assertSame(410, $secondResponse->getStatusCode());
143-
$this->assertSame('Expectation no longer applicable', $secondResponse->getBody(true));
143+
$this->assertSame('Expectation not met', $secondResponse->getBody(true));
144144

145145
$this->http->mock
146146
->exactly(2)
@@ -156,7 +156,7 @@ public function testLimitDurationOfAResponse()
156156
$this->assertSame(200, $secondResponse->getStatusCode());
157157
$thirdResponse = $this->http->client->post('/')->send();
158158
$this->assertSame(410, $thirdResponse->getStatusCode());
159-
$this->assertSame('Expectation no longer applicable', $thirdResponse->getBody(true));
159+
$this->assertSame('Expectation not met', $thirdResponse->getBody(true));
160160

161161
$this->http->mock
162162
->any()

0 commit comments

Comments
 (0)