Skip to content

Commit 5cd0d9c

Browse files
author
Brian Faust
authored
Add support for Pest test generation (#625)
1 parent 1a3e042 commit 5cd0d9c

File tree

44 files changed

+2043
-51
lines changed

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

+2043
-51
lines changed

config/blueprint.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -148,7 +148,8 @@
148148
'model' => \Blueprint\Generators\ModelGenerator::class,
149149
'route' => \Blueprint\Generators\RouteGenerator::class,
150150
'seeder' => \Blueprint\Generators\SeederGenerator::class,
151-
'test' => \Blueprint\Generators\TestGenerator::class,
151+
'test' => \Blueprint\Generators\PhpUnitTestGenerator::class,
152+
// 'test' => \Blueprint\Generators\PestTestGenerator::class,
152153
'event' => \Blueprint\Generators\Statements\EventGenerator::class,
153154
'form_request' => \Blueprint\Generators\Statements\FormRequestGenerator::class,
154155
'job' => \Blueprint\Generators\Statements\JobGenerator::class,

src/Concerns/HandlesImports.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ protected function addImport(Model $model, $class)
1515

1616
protected function buildImports(Model $model)
1717
{
18-
return collect($this->imports[$model->name()])
18+
return collect($this->imports[$model->name()] ?? [])
1919
->map(fn ($class) => "use {$class};")
2020
->unique()
2121
->sort()

src/Generators/PestTestGenerator.php

Lines changed: 642 additions & 0 deletions
Large diffs are not rendered by default.

src/Generators/TestGenerator.php renamed to src/Generators/PhpUnitTestGenerator.php

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525
use Illuminate\Support\Str;
2626
use Shift\Faker\Registry as FakerRegistry;
2727

28-
class TestGenerator extends AbstractClassGenerator implements Generator
28+
class PhpUnitTestGenerator extends AbstractClassGenerator implements Generator
2929
{
3030
use HandlesImports, HandlesTraits;
3131

@@ -47,11 +47,12 @@ public function output(Tree $tree): array
4747
{
4848
$this->tree = $tree;
4949

50-
$stub = $this->filesystem->stub('test.class.stub');
50+
$stub = $this->filesystem->stub('phpunit.test.class.stub');
5151

5252
/** @var \Blueprint\Models\Controller $controller */
5353
foreach ($tree->controllers() as $controller) {
5454
$this->addImport($controller, \Tests\TestCase::class);
55+
5556
$path = $this->getPath($controller);
5657

5758
$this->create($path, $this->populateStub($stub, $controller));
@@ -459,7 +460,7 @@ protected function buildTestCases(Controller $controller)
459460
'$response = $this->%s(route(\'%s.%s\'',
460461
$this->httpMethodForAction($name),
461462
config('blueprint.plural_routes') ? Str::plural(Str::kebab($context)) : Str::kebab($context),
462-
$name,
463+
$name
463464
);
464465

465466
if (in_array($name, ['edit', 'update', 'show', 'destroy'])) {
@@ -499,7 +500,7 @@ protected function buildTestCases(Controller $controller)
499500
private function testCaseStub()
500501
{
501502
if (empty($this->stubs['test-case'])) {
502-
$this->stubs['test-case'] = $this->filesystem->stub('test.case.stub');
503+
$this->stubs['test-case'] = $this->filesystem->stub('phpunit.test.case.stub');
503504
}
504505

505506
return $this->stubs['test-case'];

stubs/pest.pest.stub

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
<?php
2+
3+
use Illuminate\Foundation\Testing\RefreshDatabase;
4+
use JMac\Testing\Traits\AdditionalAssertions;
5+
use Tests\TestCase;
6+
7+
/*
8+
|--------------------------------------------------------------------------
9+
| Test Case
10+
|--------------------------------------------------------------------------
11+
|
12+
| The closure you provide to your test functions is always bound to a specific PHPUnit test
13+
| case class. By default, that class is "PHPUnit\Framework\TestCase". Of course, you may
14+
| need to change it using the "uses()" function to bind a different classes or traits.
15+
|
16+
*/
17+
18+
uses(
19+
TestCase::class,
20+
AdditionalAssertions::class,
21+
RefreshDatabase::class,
22+
)->in('Feature');
23+
24+
/*
25+
|--------------------------------------------------------------------------
26+
| Expectations
27+
|--------------------------------------------------------------------------
28+
|
29+
| When you're writing tests, you often need to check that values meet certain conditions. The
30+
| "expect()" function gives you access to a set of "expectations" methods that you can use
31+
| to assert different things. Of course, you may extend the Expectation API at any time.
32+
|
33+
*/
34+
35+
expect()->extend('toBeOne', function () {
36+
return $this->toBe(1);
37+
});
38+
39+
/*
40+
|--------------------------------------------------------------------------
41+
| Functions
42+
|--------------------------------------------------------------------------
43+
|
44+
| While Pest is very powerful out-of-the-box, you may have some testing code specific to your
45+
| project that you don't want to repeat in every file. Here you can also expose helpers as
46+
| global functions to help you to reduce the number of lines of code in your test files.
47+
|
48+
*/
49+
50+
function something(): void
51+
{
52+
// ..
53+
}

stubs/pest.test.case.stub

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
test('{{ method }}', function (): void {
2+
{{ body }}
3+
});

stubs/pest.test.class.stub

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
<?php
2+
3+
namespace {{ namespace }};
4+
5+
{{ imports }}
6+
7+
{{ body }}
File renamed without changes.
File renamed without changes.
Lines changed: 244 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,244 @@
1+
<?php
2+
3+
namespace Tests\Feature\Generators;
4+
5+
use Blueprint\Blueprint;
6+
use Blueprint\Generators\PestTestGenerator;
7+
use Blueprint\Lexers\StatementLexer;
8+
use Blueprint\Tree;
9+
use PHPUnit\Framework\Attributes\DataProvider;
10+
use PHPUnit\Framework\Attributes\Test;
11+
use Tests\TestCase;
12+
13+
/**
14+
* @see PestTestGenerator
15+
*/
16+
final class PestTestGeneratorTest extends TestCase
17+
{
18+
private $blueprint;
19+
20+
/** @var PestTestGenerator */
21+
private $subject;
22+
23+
protected function setUp(): void
24+
{
25+
parent::setUp();
26+
27+
$this->subject = new PestTestGenerator($this->filesystem);
28+
29+
$this->blueprint = new Blueprint();
30+
$this->blueprint->registerLexer(new \Blueprint\Lexers\ModelLexer());
31+
$this->blueprint->registerLexer(new \Blueprint\Lexers\ControllerLexer(new StatementLexer()));
32+
$this->blueprint->registerGenerator($this->subject);
33+
}
34+
35+
#[Test]
36+
public function output_writes_nothing_for_empty_tree(): void
37+
{
38+
$this->filesystem->expects('stub')
39+
->with('pest.test.class.stub')
40+
->andReturn($this->stub('pest.test.class.stub'));
41+
42+
$this->filesystem->shouldNotHaveReceived('put');
43+
44+
$this->assertEquals([], $this->subject->output(new Tree(['controllers' => []])));
45+
}
46+
47+
#[Test]
48+
#[DataProvider('controllerTreeDataProvider')]
49+
public function output_generates_test_for_controller_tree_l8($definition, $path, $test): void
50+
{
51+
$this->filesystem->expects('stub')
52+
->with('pest.test.class.stub')
53+
->andReturn($this->stub('pest.test.class.stub'));
54+
55+
$this->filesystem->expects('stub')
56+
->with('pest.test.case.stub')
57+
->andReturn($this->stub('pest.test.case.stub'));
58+
59+
$paths = collect($path)->combine($test)->toArray();
60+
foreach ($paths as $path => $test) {
61+
$dirname = dirname($path);
62+
63+
$this->filesystem->expects('exists')
64+
->with($dirname)
65+
->andReturnFalse();
66+
67+
$this->filesystem->expects('makeDirectory')
68+
->with($dirname, 0755, true);
69+
70+
$this->filesystem->expects('put')
71+
->with($path, $this->fixture($test));
72+
}
73+
74+
$tokens = $this->blueprint->parse($this->fixture($definition));
75+
$tree = $this->blueprint->analyze($tokens);
76+
77+
// dump([$definition, $path, $test]);
78+
79+
$this->subject->output($tree);
80+
81+
// $this->assertEquals(['created' => [...array_keys($paths), 'tests/Pest.php']], $this->subject->output($tree));
82+
}
83+
84+
#[Test]
85+
public function output_works_for_pascal_case_definition_l8(): void
86+
{
87+
$this->filesystem->expects('stub')
88+
->with('pest.test.class.stub')
89+
->andReturn($this->stub('pest.test.class.stub'));
90+
91+
$this->filesystem->expects('stub')
92+
->with('pest.test.case.stub')
93+
->andReturn($this->stub('pest.test.case.stub'));
94+
95+
$certificateControllerTest = 'tests/Feature/Http/Controllers/CertificateControllerTest.php';
96+
$certificateTypeControllerTest = 'tests/Feature/Http/Controllers/CertificateTypeControllerTest.php';
97+
98+
$this->filesystem->expects('exists')
99+
->with(dirname($certificateControllerTest))
100+
->andReturnTrue();
101+
102+
$this->filesystem->expects('put')
103+
->with($certificateControllerTest, $this->fixture('tests/pest/certificate-pascal-case-example.php'));
104+
105+
$this->filesystem->expects('exists')
106+
->with(dirname($certificateTypeControllerTest))
107+
->andReturnTrue();
108+
109+
$this->filesystem->expects('put')
110+
->with($certificateTypeControllerTest, $this->fixture('tests/pest/certificate-type-pascal-case-example.php'));
111+
112+
$tokens = $this->blueprint->parse($this->fixture('drafts/pascal-case.yaml'));
113+
$tree = $this->blueprint->analyze($tokens);
114+
115+
$this->assertEquals(['created' => [$certificateControllerTest, $certificateTypeControllerTest, 'tests/Pest.php']], $this->subject->output($tree));
116+
}
117+
118+
#[Test]
119+
public function output_generates_test_for_controller_tree_using_cached_model_l8(): void
120+
{
121+
$this->filesystem->expects('stub')
122+
->with('pest.test.class.stub')
123+
->andReturn($this->stub('pest.test.class.stub'));
124+
125+
$this->filesystem->expects('stub')
126+
->with('pest.test.case.stub')
127+
->andReturn($this->stub('pest.test.case.stub'));
128+
129+
$this->filesystem->expects('exists')
130+
->with('tests/Feature/Http/Controllers')
131+
->andReturnFalse();
132+
133+
$this->filesystem->expects('makeDirectory')
134+
->with('tests/Feature/Http/Controllers', 0755, true);
135+
136+
$this->filesystem->expects('put')
137+
->with('tests/Feature/Http/Controllers/UserControllerTest.php', $this->fixture('tests/pest/reference-cache.php'));
138+
139+
$tokens = $this->blueprint->parse($this->fixture('drafts/reference-cache.yaml'));
140+
$tokens['cache'] = [
141+
'User' => [
142+
'email' => 'string',
143+
'password' => 'string',
144+
],
145+
];
146+
$tree = $this->blueprint->analyze($tokens);
147+
148+
$this->assertEquals(['created' => ['tests/Feature/Http/Controllers/UserControllerTest.php', 'tests/Pest.php']], $this->subject->output($tree));
149+
}
150+
151+
#[Test]
152+
public function output_generates_tests_with_models_with_custom_namespace_correctly_l8(): void
153+
{
154+
$definition = 'drafts/models-with-custom-namespace.yaml';
155+
$path = 'tests/Feature/Http/Controllers/CategoryControllerTest.php';
156+
$test = 'tests/pest/models-with-custom-namespace.php';
157+
158+
$this->app['config']->set('blueprint.models_namespace', 'Models');
159+
160+
$this->filesystem->expects('stub')
161+
->with('pest.test.class.stub')
162+
->andReturn($this->stub('pest.test.class.stub'));
163+
164+
$this->filesystem->expects('stub')
165+
->with('pest.test.case.stub')
166+
->andReturn($this->stub('pest.test.case.stub'));
167+
168+
$dirname = dirname($path);
169+
$this->filesystem->expects('exists')
170+
->with($dirname)
171+
->andReturnFalse();
172+
173+
$this->filesystem->expects('makeDirectory')
174+
->with($dirname, 0755, true);
175+
176+
$this->filesystem->expects('put')
177+
->with($path, $this->fixture($test));
178+
179+
$tokens = $this->blueprint->parse($this->fixture($definition));
180+
$tree = $this->blueprint->analyze($tokens);
181+
182+
$this->assertEquals(['created' => [$path, 'tests/Pest.php']], $this->subject->output($tree));
183+
}
184+
185+
#[Test]
186+
public function output_generates_tests_with_pluralized_route_names(): void
187+
{
188+
$definition = 'drafts/models-with-custom-namespace.yaml';
189+
$path = 'tests/Feature/Http/Controllers/CategoryControllerTest.php';
190+
$test = 'tests/pest/routes-with-pluralized-names.php';
191+
192+
$this->app['config']->set('blueprint.models_namespace', 'Models');
193+
$this->app['config']->set('blueprint.plural_routes', true);
194+
195+
$this->filesystem->expects('stub')
196+
->with('pest.test.class.stub')
197+
->andReturn($this->stub('pest.test.class.stub'));
198+
199+
$this->filesystem->expects('stub')
200+
->with('pest.test.case.stub')
201+
->andReturn($this->stub('pest.test.case.stub'));
202+
203+
$dirname = dirname($path);
204+
$this->filesystem->expects('exists')
205+
->with($dirname)
206+
->andReturnFalse();
207+
208+
$this->filesystem->expects('makeDirectory')
209+
->with($dirname, 0755, true);
210+
211+
$this->filesystem->expects('put')
212+
->with($path, $this->fixture($test));
213+
214+
$tokens = $this->blueprint->parse($this->fixture($definition));
215+
$tree = $this->blueprint->analyze($tokens);
216+
217+
$this->assertEquals(['created' => [$path, 'tests/Pest.php']], $this->subject->output($tree));
218+
}
219+
220+
public static function controllerTreeDataProvider(): array
221+
{
222+
return [
223+
['drafts/readme-example.yaml', 'tests/Feature/Http/Controllers/PostControllerTest.php', 'tests/pest/readme-example.php'],
224+
['drafts/readme-example-notification-facade.yaml', 'tests/Feature/Http/Controllers/PostControllerTest.php', 'tests/pest/readme-example-notification.php'],
225+
['drafts/readme-example-notification-model.yaml', 'tests/Feature/Http/Controllers/PostControllerTest.php', 'tests/pest/readme-example-notification.php'],
226+
['drafts/respond-statements.yaml', 'tests/Feature/Http/Controllers/Api/PostControllerTest.php', 'tests/pest/respond-statements.php'],
227+
['drafts/full-crud-example.yaml', 'tests/Feature/Http/Controllers/PostControllerTest.php', 'tests/pest/full-crud-example.php'],
228+
['drafts/crud-show-only.yaml', 'tests/Feature/Http/Controllers/PostControllerTest.php', 'tests/pest/crud-show-only.php'],
229+
['drafts/model-reference-validate.yaml', 'tests/Feature/Http/Controllers/CertificateControllerTest.php', 'tests/pest/api-shorthand-validation.php'],
230+
['drafts/controllers-only-no-context.yaml', 'tests/Feature/Http/Controllers/ReportControllerTest.php', 'tests/pest/controllers-only-no-context.php'],
231+
['drafts/call-to-a-member-function-columns-on-null.yaml', [
232+
'tests/Feature/Http/Controllers/SubscriptionControllerTest.php',
233+
'tests/Feature/Http/Controllers/TelegramControllerTest.php',
234+
'tests/Feature/Http/Controllers/PaymentControllerTest.php',
235+
'tests/Feature/Http/Controllers/Api/PaymentControllerTest.php',
236+
], [
237+
'tests/pest/call-to-a-member-function-columns-on-null-SubscriptionControllerTest.php',
238+
'tests/pest/call-to-a-member-function-columns-on-null-TelegramControllerTest.php',
239+
'tests/pest/call-to-a-member-function-columns-on-null-PaymentControllerTest.php',
240+
'tests/pest/call-to-a-member-function-columns-on-null-Api-PaymentControllerTest.php',
241+
]],
242+
];
243+
}
244+
}

0 commit comments

Comments
 (0)