Skip to content

Commit 40fbad4

Browse files
committed
FEATURE: Custom VideoRecorderClient & helpfull TestCaseTrait added
1 parent be4f739 commit 40fbad4

File tree

9 files changed

+678
-22
lines changed

9 files changed

+678
-22
lines changed

README.md

Lines changed: 87 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ VCRBundle
33

44
Integrates [php-vcr](https://github.com/php-vcr/php-vcr) into Symfony and its
55
web profiler.
6+
It also provides a VideoRecorderBrowser for testing purpose with extra helper methods handling php-vcr recordings.
67

78
<img src="https://cloud.githubusercontent.com/assets/66958/5232274/b841676e-774b-11e4-8f4e-1f3e8cb7739e.png" width="280" height="175" alt="PHP-VCR Symfony web profiler panel"/>
89
<img src="https://cloud.githubusercontent.com/assets/66958/5232275/b84288d8-774b-11e4-803c-7b72f75e59b0.png" width="280" height="175" alt="PHP-VCR Symfony web profiler panel - request details"/>
@@ -20,41 +21,107 @@ composer require php-vcr/vcr-bundle
2021
And declare the bundle in your `config/bundles.php` file:
2122

2223
```php
24+
<?php
25+
declare(strict_types = 1);
26+
2327
return [
2428
// ...
2529
VCR\VCRBundle\VCRBundle::class => ['test' => true],
2630
];
31+
```
32+
33+
## Usage
34+
35+
Enable the required library hooks for your purpose and write test cases.
36+
37+
### VideoRecorderBrowser (without Trait)
38+
39+
```php
40+
<?php
41+
declare(strict_types = 1);
42+
43+
class ExampleTest extends \VCR\VCRBundle\Tests\Functional\WebTestCase
44+
{
45+
public function test(): void
46+
{
47+
$kernel = static::bootKernel();
48+
/** @var \VCR\VCRBundle\VideoRecorderBrowser $client */
49+
$client = $kernel->getContainer()->get('test.client.vcr');
50+
51+
$client->insertVideoRecorderCassette('my-test-cassette-name');
52+
53+
// this is an example, normally services inside you project do stuff like this and you trigger them by
54+
// execute requests via the KernelBrowser client
55+
file_get_contents('https://www.google.de');
56+
57+
// cassette.path is configured to '%kernel.project_dir%/tests/Fixtures'
58+
// recordings are written to %kernel.project_dir%/tests/Fixtures/my-test-cassette-name
59+
// cassette.path + cassetteName (done by inserting the cassette)
60+
}
61+
}
62+
```
63+
64+
### VideoRecorderBrowser (with Trait)
65+
66+
```php
67+
<?php
68+
declare(strict_types = 1);
69+
70+
namespace MyCompany\MyProject\Tests\Functional;
71+
72+
class ExampleTest extends \VCR\VCRBundle\Tests\Functional\WebTestCase
73+
{
74+
use \VCR\VCRBundle\Test\VCRTestCaseTrait;
75+
76+
/**
77+
* Specify a namespace prefix which should be ignored while generating the base path for this test case.
78+
*/
79+
protected $ignoredTestSuiteNamespacePrefix = 'MyCompany\\MyProject\\Tests\\';
2780

81+
public function test(): void
82+
{
83+
/** @var \VCR\VCRBundle\VideoRecorderBrowser $client */
84+
$client = static::createVideoRecorderClient();
85+
86+
// this is an example, normally services inside you project do stuff like this and you trigger them by
87+
// execute requests via the KernelBrowser client
88+
file_get_contents('https://www.google.de');
89+
90+
// cassette.path is configured to '%kernel.project_dir%/tests/Fixtures'
91+
// recordings are written to %kernel.project_dir%/tests/Fixtures/Functional/ExampleTest/test
92+
// cassette.path + TestCasePath (- ignoredTestSuiteNamespacePrefix) + TestName
93+
}
94+
}
2895
```
2996

3097
## Configuration reference
3198

3299
```yaml
33100
vcr:
34-
enabled: true
35-
library_hooks:
36-
stream_wrapper: false
37-
curl: false
38-
soap: false
39-
request_matchers:
40-
method: true
41-
url: true
42-
query_string: true
43-
host: true
44-
headers: true
45-
body: true
46-
post_fields: true
47-
cassette:
48-
type: json
49-
path: '%kernel.cache_dir%/vcr'
50-
name: vcr
101+
enabled: true
102+
library_hooks:
103+
stream_wrapper: false
104+
curl: false
105+
soap: false
106+
request_matchers:
107+
method: true
108+
url: true
109+
query_string: true
110+
host: true
111+
headers: true
112+
body: true
113+
post_fields: true
114+
cassette:
115+
type: json
116+
path: '%kernel.cache_dir%/vcr'
117+
name: vcr
51118
```
52119
53120
## Credits
54121
55-
* [Kévin Gomez](http://github.com/K-Phoen/)
56-
* [Ludovic Fleury](https://github.com/ludofleury) - to whom I borrowed the
57-
design of the web profiler part from his [GuzzleBundle](https://github.com/ludofleury/GuzzleBundle/).
122+
* [Kévin Gomez](http://github.com/K-Phoen/)
123+
* [Ludovic Fleury](https://github.com/ludofleury) - to whom I borrowed the
124+
design of the web profiler part from his [GuzzleBundle](https://github.com/ludofleury/GuzzleBundle/).
58125
59126
## License
60127

composer.json

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,14 @@
77
{
88
"name": "Kévin Gomez",
99
"email": "[email protected]"
10+
},
11+
{
12+
"name": "Daniel Hürtgen",
13+
"email": "[email protected]"
14+
},
15+
{
16+
"name": "Simon Hübner",
17+
"email": "[email protected]"
1018
}
1119
],
1220
"config": {
@@ -16,18 +24,20 @@
1624
"php": "^7.2|^8",
1725
"php-vcr/php-vcr": "^1.5",
1826
"symfony/config": "^4|^5",
27+
"symfony/browser-kit": "^4|^5",
1928
"symfony/dependency-injection": "^4|^5",
2029
"symfony/filesystem": "^4|^5",
2130
"symfony/event-dispatcher": "^4|^5",
31+
"symfony/framework-bundle": "^4.4|^5.4",
2232
"symfony/http-foundation": "^4|^5",
2333
"symfony/http-kernel": "^4.4|^5",
34+
"symfony/polyfill-php80": "^1.16",
2435
"symfony/yaml": "^4|^5"
2536
},
2637
"require-dev": {
2738
"dms/phpunit-arraysubset-asserts": "^0.4",
2839
"neutron/temporary-filesystem": "^3",
2940
"phpunit/phpunit": "^8.5|^9.5",
30-
"symfony/framework-bundle": "^4.4|^5.4",
3141
"symfony/phpunit-bridge": "^4.4|^5.4"
3242
},
3343
"autoload": {

src/Resources/config/services.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,3 +38,7 @@ services:
3838
calls:
3939
- [ setEventDispatcher, [ '@event_dispatcher' ] ]
4040
public: true
41+
42+
test.client.vcr:
43+
class: VCR\VCRBundle\VideoRecorderBrowser
44+
parent: test.client

src/Test/VCRTestCaseTrait.php

Lines changed: 211 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,211 @@
1+
<?php
2+
declare(strict_types = 1);
3+
4+
namespace VCR\VCRBundle\Test;
5+
6+
use Psr\Container\ContainerInterface;
7+
use Symfony\Bundle\FrameworkBundle\KernelBrowser;
8+
use Symfony\Component\DependencyInjection\Exception\ServiceNotFoundException;
9+
use VCR\VCRBundle\VideoRecorderBrowser;
10+
use VCR\Videorecorder;
11+
12+
/**
13+
* Trait providing helper functions to work with \PHPUnit\Framework\TestCase in combination with
14+
* \Symfony\Bundle\FrameworkBundle\Test\WebTestCase and the php-vcr library (&bundle).
15+
*
16+
* @method ?string getName(bool $withDataSet)
17+
* @property ?string $ignoredTestSuiteNamespacePrefix Set a namespace prefix which should be ignored while generating
18+
* the cassette path
19+
*/
20+
trait VCRTestCaseTrait
21+
{
22+
/**
23+
* @var null|string
24+
*/
25+
protected $testSuiteName;
26+
27+
/**
28+
*
29+
*
30+
* @return string The current test suite name
31+
*/
32+
public function getTestSuiteName(): string
33+
{
34+
$this->setUpTestSuiteName();
35+
36+
return $this->testSuiteName;
37+
}
38+
39+
/**
40+
* @beforeClass
41+
*
42+
* @return void
43+
*/
44+
protected function setUpTestSuiteName(): void
45+
{
46+
if (! $this->testSuiteName) {
47+
$testSuiteName = (new \ReflectionClass($this))->getName();
48+
if (
49+
! empty($this->ignoredTestSuiteNamespacePrefix) &&
50+
str_starts_with($testSuiteName, $this->ignoredTestSuiteNamespacePrefix)
51+
) {
52+
$testSuiteName = str_replace($this->ignoredTestSuiteNamespacePrefix, '', $testSuiteName);
53+
}
54+
$testSuiteNameParts = array_filter(explode('\\', $testSuiteName));
55+
$this->testSuiteName = implode(DIRECTORY_SEPARATOR, $testSuiteNameParts);
56+
}
57+
}
58+
59+
/**
60+
* Normalize a video recorder cassette name.
61+
*
62+
* @param string $name The name to normalize
63+
*
64+
* @return string The normalized cassette name
65+
*/
66+
protected function normalizeVideoRecorderCassetteName(string $name): string
67+
{
68+
return preg_replace(['/\s+/', '/[^a-zA-Z0-9\-_]/'], ['-', ''], $name);
69+
}
70+
71+
/**
72+
* Get the video recorder cassette name from the current test.
73+
*
74+
* @param null|string $suffix
75+
* @param bool $withDataSet With dataset
76+
*
77+
* @return string
78+
*/
79+
protected function getVideoRecorderCassetteName(string $suffix = null, bool $withDataSet = true): string
80+
{
81+
$name = $this->getName($withDataSet);
82+
if (! empty($suffix)) {
83+
$name .= $suffix;
84+
}
85+
86+
return $this->normalizeVideoRecorderCassetteName($name);
87+
}
88+
89+
/**
90+
* Enable VCR VideoRecorder.
91+
*
92+
* @param ContainerInterface $container
93+
* @param string|null $suffix
94+
*
95+
* @param bool $withDataSet
96+
*
97+
* @return void
98+
*/
99+
protected function enableVideoRecorder(
100+
ContainerInterface $container,
101+
string $suffix = null,
102+
bool $withDataSet = true
103+
): void {
104+
$videoRecorder = $this->getVideoRecorder($container);
105+
$videoRecorder->turnOn();
106+
$this->insertVideoRecorderCassette($container, $suffix, $withDataSet);
107+
}
108+
109+
/**
110+
* Insert cassette into VCR VideoRecorder.
111+
*
112+
* @param ContainerInterface $container
113+
* @param null|string $suffix
114+
*
115+
* @param bool $withDataSet
116+
*
117+
* @return void
118+
*/
119+
protected function insertVideoRecorderCassette(
120+
ContainerInterface $container,
121+
string $suffix = null,
122+
bool $withDataSet = true
123+
): void {
124+
$name = $this->getVideoRecorderCassetteName($suffix, $withDataSet);
125+
$name = implode(
126+
DIRECTORY_SEPARATOR,
127+
[
128+
$this->getTestSuiteName(),
129+
$name,
130+
]
131+
);
132+
133+
$videoRecorder = $this->getVideoRecorder($container);
134+
135+
$videoRecorder->insertCassette($name);
136+
}
137+
138+
/**
139+
* Insert default cassette into VCR VideoRecorder.
140+
*
141+
* @param ContainerInterface $container
142+
*
143+
* @return void
144+
*/
145+
protected function insertDefaultVideoRecorderCassette(ContainerInterface $container): void
146+
{
147+
$defaultCassetteName = $container->getParameter('vcr.cassette.name');
148+
149+
$this->getVideoRecorder($container)->insertCassette($defaultCassetteName);
150+
}
151+
152+
/**
153+
* Disable VCR VideoRecorder.
154+
*
155+
* @param ContainerInterface $container
156+
*
157+
* @return void
158+
*/
159+
protected function disableVideoRecorder(ContainerInterface $container): void
160+
{
161+
$this->getVideoRecorder($container)->turnOff();
162+
}
163+
164+
/**
165+
* @param ContainerInterface $container
166+
*
167+
* @return Videorecorder
168+
*/
169+
protected function getVideoRecorder(ContainerInterface $container): Videorecorder
170+
{
171+
return $container->get('vcr.recorder');
172+
}
173+
174+
/**
175+
* Creates a Client supports the VideoRecorder framework.
176+
*
177+
* @param array $options An array of options to pass to the createKernel method
178+
* @param array $server An array of server parameters
179+
* @param null|string $suffix
180+
* @param bool $withDataSet
181+
*
182+
* @return VideoRecorderBrowser A KernelBrowser instance
183+
*/
184+
protected function createVideoRecorderClient(
185+
array $options = [],
186+
array $server = [],
187+
string $suffix = null,
188+
bool $withDataSet = true
189+
) {
190+
if (! \method_exists(\get_called_class(), 'createClient')) {
191+
throw new \LogicException(
192+
'Current test case has no static method to createClient(array $options, array $server).'
193+
);
194+
}
195+
196+
/** @var KernelBrowser $client */
197+
$client = static::createClient($options, $server);
198+
try {
199+
/** @var VideoRecorderBrowser $client */
200+
$client = $client->getKernel()->getContainer()->get('test.client.vcr');
201+
} catch (ServiceNotFoundException $e) {
202+
throw new \LogicException('VideoRecorderBrowser not loaded. Did you enable the this bundle?');
203+
}
204+
205+
$client->setVideoRecorderCassetteBasePath($this->getTestSuiteName());
206+
$client->enableVideoRecorder();
207+
$client->insertVideoRecorderCassette($this->getVideoRecorderCassetteName($suffix, $withDataSet));
208+
209+
return $client;
210+
}
211+
}

0 commit comments

Comments
 (0)