Skip to content

Commit dc459ed

Browse files
committed
Add SqlStatementExpectations and enhance MockClientBuilder
1 parent 2123f4c commit dc459ed

File tree

9 files changed

+365
-38
lines changed

9 files changed

+365
-38
lines changed

README.md

Lines changed: 46 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ final class MyTest extends TestCase
3939
{
4040
protected function getClient() : Client
4141
{
42-
// you have to implement this method
42+
// TODO: Implement getClient() method.
4343
}
4444

4545
// ...
@@ -162,7 +162,7 @@ public function testChangeUserPassword() : void
162162

163163
```php
164164
/**
165-
* @requires Tarantool >= 2.3.2
165+
* @requires Tarantool ^2.3.2
166166
*/
167167
public function testPrepareCreatesPreparedStatement() : void
168168
{
@@ -212,6 +212,32 @@ public function testGetSpaceIsCached() : void
212212
}
213213
```
214214

215+
In order to check SQL statements, use the `Tarantool\PhpUnit\Expectation\SqlStatementExpectations` trait,
216+
which contains the following methods:
217+
218+
* `expectSqlStatementToBeExecuted(int $count) : void`
219+
* `expectSqlStatementToBeExecutedAtLeast(int $count) : void`
220+
* `expectSqlStatementToBeExecutedAtMost(int $count) : void`
221+
* `expectSqlStatementToBeExecutedOnce() : void`
222+
* `expectSqlStatementToBeNeverCalled() : void`
223+
* `expectSqlStatementToBeExecutedAtLeastOnce() : void`
224+
* `expectSqlStatementToBeExecutedAtMostOnce() : void`
225+
226+
Usage example:
227+
228+
```php
229+
public function testCloseDeallocatesPreparedStatement() : void
230+
{
231+
$stmt = $this->client->prepare('SELECT ?');
232+
233+
$this->expectSqlStatementToBeExecutedOnce();
234+
$stmt->close();
235+
}
236+
```
237+
238+
To enable all the above expectation methods in one go, use the `Tarantool\PhpUnit\Expectation\Expectations` trait,
239+
or extend the `Tarantool\PhpUnit\TestCase` class.
240+
215241

216242
## Mocking
217243

@@ -249,23 +275,20 @@ public function testFoo() : void
249275

250276
To simulate specific scenarios, such as establishing a connection to a server
251277
or returning specific responses in a specific order from the server, use the facilities
252-
of the `MockClientBuilder` class. For example, to simulate `PING` request/response:
278+
of the `MockClientBuilder` class. For example, to simulate the `PING` request:
253279

254280
```php
255281
use Tarantool\Client\Request\PingRequest;
256-
use Tarantool\PhpUnit\Client\DummyFactory;
257282
use Tarantool\PhpUnit\TestCase;
258283

259284
final class MyTest extends TestCase
260285
{
261286
public function testFoo() : void
262287
{
263288
$mockClient = $this->getMockClientBuilder()
264-
->shouldHandle(
265-
new PingRequest(),
266-
DummyFactory::createEmptyResponse()
267-
)->build();
268-
289+
->shouldSend(new PingRequest())
290+
->build();
291+
269292
// ...
270293
}
271294

@@ -285,8 +308,10 @@ final class MyTest extends TestCase
285308
public function testFoo() : void
286309
{
287310
$mockClient = $this->getMockClientBuilder()
288-
->shouldHandle(
289-
RequestTypes::EVALUATE,
311+
->shouldSend(
312+
RequestTypes::EVALUATE,
313+
RequestTypes::EVALUATE
314+
)->willReceive(
290315
DummyFactory::createResponseFromData([2]),
291316
DummyFactory::createResponseFromData([3])
292317
)->build();
@@ -297,6 +322,16 @@ final class MyTest extends TestCase
297322
// ...
298323
}
299324
```
325+
The above example can be simplified to:
326+
327+
```php
328+
$mockClient = $this->getMockClientBuilder()
329+
->shouldHandle(
330+
RequestTypes::EVALUATE,
331+
DummyFactory::createResponseFromData([2]),
332+
DummyFactory::createResponseFromData([3])
333+
)->build();
334+
```
300335

301336
Besides, the builder allows setting custom `Connection` and `Packer` instances:
302337

psalm.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
<errorLevel type="suppress">
2020
<referencedMethod name="PHPUnit\Framework\MockObject\Builder\InvocationMocker::method" />
2121
<referencedMethod name="PHPUnit\Framework\MockObject\Builder\InvocationMocker::with" />
22+
<referencedMethod name="PHPUnit\Framework\MockObject\Builder\InvocationMocker::withConsecutive" />
2223
<referencedMethod name="PHPUnit\Framework\MockObject\Builder\InvocationMocker::willReturn" />
2324
<referencedMethod name="PHPUnit\Framework\MockObject\Builder\InvocationMocker::willReturnOnConsecutiveCalls" />
2425
<referencedMethod name="PHPUnit\Framework\TestCase::__construct" />

src/Client/MockClientBuilder.php

Lines changed: 44 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -28,19 +28,25 @@ final class MockClientBuilder
2828
/** @var TestCase */
2929
private $testCase;
3030

31-
/** @var \SplObjectStorage<object, array<int, Response>> */
31+
/** @var array<int, Request>|null */
3232
private $requests;
3333

34+
/** @var array<int, Response> */
35+
private $responses;
36+
3437
/** @var Connection|null */
3538
private $connection;
3639

3740
/** @var Packer|null */
3841
private $packer;
3942

43+
/** @var int|null */
44+
private $shouldBeCalledTimes = null;
45+
4046
public function __construct(TestCase $testCase)
4147
{
4248
$this->testCase = $testCase;
43-
$this->requests = new \SplObjectStorage();
49+
$this->responses = [DummyFactory::createEmptyResponse()];
4450
}
4551

4652
public static function buildDefault() : Client
@@ -52,17 +58,41 @@ public static function buildDefault() : Client
5258
return $self->build();
5359
}
5460

61+
/**
62+
* @param Request|Constraint|int $request
63+
* @param Request|Constraint|int ...$requests
64+
*/
65+
public function shouldSend($request, ...$requests) : self
66+
{
67+
$this->requests = [];
68+
foreach (\func_get_args() as $arg) {
69+
$this->requests[] = \is_int($arg) ? new IsRequestType($arg) : $arg;
70+
}
71+
72+
$this->shouldBeCalledTimes = \count($this->requests);
73+
74+
return $this;
75+
}
76+
5577
/**
5678
* @param Request|Constraint|int $request
5779
* @param Response ...$responses
5880
*/
5981
public function shouldHandle($request, ...$responses) : self
6082
{
61-
if (\is_int($request)) {
62-
$request = new IsRequestType($request);
83+
$this->shouldSend($request);
84+
$this->willReceive(...$responses);
85+
86+
if ($responses) {
87+
$this->shouldBeCalledTimes = \count($responses);
6388
}
6489

65-
$this->requests->attach($request, $responses);
90+
return $this;
91+
}
92+
93+
public function willReceive(Response $response, Response ...$responses) : self
94+
{
95+
$this->responses = \func_get_args();
6696

6797
return $this;
6898
}
@@ -99,23 +129,18 @@ private function createHandler() : MockObject
99129
$packer = $this->createPacker();
100130
$handler->method('getPacker')->willReturn($packer);
101131

102-
$defaultResponse = DummyFactory::createEmptyResponse();
132+
$handleMocker = null !== $this->shouldBeCalledTimes
133+
? $handler->expects(TestCase::exactly($this->shouldBeCalledTimes))->method('handle')
134+
: $handler->method('handle');
103135

104-
if (!$this->requests->count()) {
105-
$handler->method('handle')->willReturn($defaultResponse);
106-
107-
return $handler;
136+
if ($this->requests) {
137+
$handleMocker->withConsecutive(...array_chunk($this->requests, 1));
108138
}
109139

110-
foreach ($this->requests as $request) {
111-
if (!$responses = $this->requests->getInfo()) {
112-
$handler->method('handle')->with($request)->willReturn($defaultResponse);
113-
continue;
114-
}
115-
116-
$handler->expects(TestCase::exactly(\count($responses)))
117-
->method('handle')->with($request)
118-
->willReturnOnConsecutiveCalls(...$responses);
140+
if (1 === \count($this->responses)) {
141+
$handleMocker->willReturn($this->responses[0]);
142+
} else {
143+
$handleMocker->willReturnOnConsecutiveCalls(...$this->responses);
119144
}
120145

121146
return $handler;

src/Expectation/Expectations.php

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
<?php
2+
3+
/**
4+
* This file is part of the tarantool/phpunit-extras package.
5+
*
6+
* (c) Eugene Leonovich <gen.work@gmail.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
declare(strict_types=1);
13+
14+
namespace Tarantool\PhpUnit\Expectation;
15+
16+
trait Expectations
17+
{
18+
use RequestExpectations;
19+
use SqlStatementExpectations;
20+
}
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
<?php
2+
3+
/**
4+
* This file is part of the tarantool/phpunit-extras package.
5+
*
6+
* (c) Eugene Leonovich <gen.work@gmail.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
declare(strict_types=1);
13+
14+
namespace Tarantool\PhpUnit\Expectation\ExpressionContext;
15+
16+
use PHPUnitExtras\Expectation\ExpressionContext;
17+
use Tarantool\Client\Client;
18+
19+
final class SqlStatementCountContext implements ExpressionContext
20+
{
21+
/** @var Client */
22+
private $client;
23+
24+
/** @var string */
25+
private $expression;
26+
27+
/** @var int */
28+
private $initialValue;
29+
30+
private function __construct(Client $client, string $expression)
31+
{
32+
$this->client = $client;
33+
$this->expression = $expression;
34+
$this->initialValue = $this->getValue();
35+
}
36+
37+
public static function exactly(Client $client, int $count) : self
38+
{
39+
return new self($client, "new_count === old_count + $count");
40+
}
41+
42+
public static function atLeast(Client $client, int $count) : self
43+
{
44+
return new self($client, "new_count >= old_count + $count");
45+
}
46+
47+
public static function atMost(Client $client, int $count) : self
48+
{
49+
return new self($client, "new_count <= old_count + $count");
50+
}
51+
52+
public function getExpression() : string
53+
{
54+
return $this->expression;
55+
}
56+
57+
public function getValues() : array
58+
{
59+
return [
60+
'old_count' => $this->initialValue,
61+
'new_count' => $this->getValue(),
62+
];
63+
}
64+
65+
private function getValue() : int
66+
{
67+
return $this->client->evaluate('return box.info.sql().cache.stmt_count')[0];
68+
}
69+
}
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
<?php
2+
3+
/**
4+
* This file is part of the tarantool/phpunit-extras package.
5+
*
6+
* (c) Eugene Leonovich <gen.work@gmail.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
declare(strict_types=1);
13+
14+
namespace Tarantool\PhpUnit\Expectation;
15+
16+
use PHPUnitExtras\Expectation\ExpressionExpectation;
17+
use Tarantool\Client\Client;
18+
use Tarantool\PhpUnit\Expectation\ExpressionContext\SqlStatementCountContext;
19+
20+
trait SqlStatementExpectations
21+
{
22+
public function expectSqlStatementToBeExecuted(int $count) : void
23+
{
24+
$context = SqlStatementCountContext::exactly($this->getClient(), $count);
25+
$this->expect(new ExpressionExpectation($context));
26+
}
27+
28+
public function expectSqlStatementToBeExecutedAtLeast(int $count) : void
29+
{
30+
$context = SqlStatementCountContext::atLeast($this->getClient(), $count);
31+
$this->expect(new ExpressionExpectation($context));
32+
}
33+
34+
public function expectSqlStatementToBeExecutedAtMost(int $count) : void
35+
{
36+
$context = SqlStatementCountContext::atMost($this->getClient(), $count);
37+
$this->expect(new ExpressionExpectation($context));
38+
}
39+
40+
public function expectSqlStatementToBeExecutedOnce() : void
41+
{
42+
$this->expectSqlStatementToBeExecuted(1);
43+
}
44+
45+
public function expectSqlStatementToBeNeverCalled() : void
46+
{
47+
$this->expectSqlStatementToBeExecuted(0);
48+
}
49+
50+
public function expectSqlStatementToBeExecutedAtLeastOnce() : void
51+
{
52+
$this->expectSqlStatementToBeExecutedAtLeast(1);
53+
}
54+
55+
public function expectSqlStatementToBeExecutedAtMostOnce() : void
56+
{
57+
$this->expectSqlStatementToBeExecutedAtMost(1);
58+
}
59+
60+
abstract protected function getClient() : Client;
61+
}

src/TestCase.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,11 @@
1616
use PHPUnitExtras\TestCase as BaseTestCase;
1717
use Tarantool\PhpUnit\Annotation\Annotations;
1818
use Tarantool\PhpUnit\Client\ClientMocking;
19-
use Tarantool\PhpUnit\Expectation\RequestExpectations;
19+
use Tarantool\PhpUnit\Expectation\Expectations;
2020

2121
abstract class TestCase extends BaseTestCase
2222
{
2323
use ClientMocking;
2424
use Annotations;
25-
use RequestExpectations;
25+
use Expectations;
2626
}

0 commit comments

Comments
 (0)