Skip to content

Commit 477a222

Browse files
committed
[make:*] add ability to generate unit tests
1 parent be24be4 commit 477a222

File tree

12 files changed

+523
-2
lines changed

12 files changed

+523
-2
lines changed
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony MakerBundle package.
5+
*
6+
* (c) Fabien Potencier <[email protected]>
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+
namespace Symfony\Bundle\MakerBundle\Maker\Common;
13+
14+
use Symfony\Bundle\MakerBundle\ConsoleStyle;
15+
use Symfony\Bundle\MakerBundle\Exception\RuntimeCommandException;
16+
use Symfony\Component\Console\Command\Command;
17+
use Symfony\Component\Console\Input\InputInterface;
18+
use Symfony\Component\Console\Input\InputOption;
19+
20+
/**
21+
* @author Jesse Rushlow <[email protected]>
22+
*
23+
* @internal
24+
*/
25+
trait CanGenerateTestsTrait
26+
{
27+
private bool $generateTests = false;
28+
29+
public function configureCommandWithTestsOption(Command $command): Command
30+
{
31+
$testsHelp = file_get_contents(\dirname(__DIR__, 2).'/Resources/help/_WithTests.txt');
32+
$help = $command->getHelp()."\n".$testsHelp;
33+
34+
$command
35+
->addOption(name: 'with-tests', mode: InputOption::VALUE_NONE, description: 'Generate PHPUnit Tests')
36+
->setHelp($help)
37+
;
38+
39+
return $command;
40+
}
41+
42+
public function interactSetGenerateTests(InputInterface $input, ConsoleStyle $io): void
43+
{
44+
// Sanity check for maker dev's - End user should never see this.
45+
if (!$input->hasOption('with-tests')) {
46+
throw new RuntimeCommandException('Whoops! "--with-tests" option does not exist. Call "addWithTestsOptions()" in the makers "configureCommand().');
47+
}
48+
49+
$this->generateTests = $input->getOption('with-tests');
50+
51+
if (!$this->generateTests) {
52+
$this->generateTests = $io->confirm('Do you want to generate PHPUnit tests? [Experimental]', false);
53+
}
54+
}
55+
56+
public function shouldGenerateTests(): bool
57+
{
58+
return $this->generateTests;
59+
}
60+
}

src/Maker/MakeCrud.php

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
use Symfony\Bundle\MakerBundle\Doctrine\DoctrineHelper;
2525
use Symfony\Bundle\MakerBundle\Generator;
2626
use Symfony\Bundle\MakerBundle\InputConfiguration;
27+
use Symfony\Bundle\MakerBundle\Maker\Common\CanGenerateTestsTrait;
2728
use Symfony\Bundle\MakerBundle\Renderer\FormTypeRenderer;
2829
use Symfony\Bundle\MakerBundle\Str;
2930
use Symfony\Bundle\MakerBundle\Util\UseStatementGenerator;
@@ -45,6 +46,8 @@
4546
*/
4647
final class MakeCrud extends AbstractMaker
4748
{
49+
use CanGenerateTestsTrait;
50+
4851
private Inflector $inflector;
4952
private string $controllerClassName;
5053
private bool $generateTests = false;
@@ -72,6 +75,7 @@ public function configureCommand(Command $command, InputConfiguration $inputConf
7275
;
7376

7477
$inputConfig->setArgumentAsNonInteractive('entity-class');
78+
$this->configureCommandWithTestsOption($command);
7579
}
7680

7781
public function interact(InputInterface $input, ConsoleStyle $io, Command $command): void
@@ -96,7 +100,7 @@ public function interact(InputInterface $input, ConsoleStyle $io, Command $comma
96100
$defaultControllerClass
97101
);
98102

99-
$this->generateTests = $io->confirm('Do you want to generate tests for the controller? [Experimental]', false);
103+
$this->interactSetGenerateTests($input, $io);
100104
}
101105

102106
public function generate(InputInterface $input, ConsoleStyle $io, Generator $generator): void
@@ -237,7 +241,7 @@ public function generate(InputInterface $input, ConsoleStyle $io, Generator $gen
237241
);
238242
}
239243

240-
if ($this->generateTests) {
244+
if ($this->shouldGenerateTests()) {
241245
$testClassDetails = $generator->createClassNameDetails(
242246
$entityClassDetails->getRelativeNameWithoutSuffix(),
243247
'Test\\Controller\\',

src/Maker/MakeRegistrationForm.php

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,18 +12,22 @@
1212
namespace Symfony\Bundle\MakerBundle\Maker;
1313

1414
use Doctrine\Bundle\DoctrineBundle\DoctrineBundle;
15+
use Doctrine\ORM\EntityManager;
1516
use Doctrine\ORM\EntityManagerInterface;
1617
use Doctrine\ORM\Mapping\Column;
1718
use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
1819
use Symfony\Bridge\Twig\Mime\TemplatedEmail;
1920
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
21+
use Symfony\Bundle\FrameworkBundle\KernelBrowser;
22+
use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
2023
use Symfony\Bundle\MakerBundle\ConsoleStyle;
2124
use Symfony\Bundle\MakerBundle\DependencyBuilder;
2225
use Symfony\Bundle\MakerBundle\Doctrine\DoctrineHelper;
2326
use Symfony\Bundle\MakerBundle\Exception\RuntimeCommandException;
2427
use Symfony\Bundle\MakerBundle\FileManager;
2528
use Symfony\Bundle\MakerBundle\Generator;
2629
use Symfony\Bundle\MakerBundle\InputConfiguration;
30+
use Symfony\Bundle\MakerBundle\Maker\Common\CanGenerateTestsTrait;
2731
use Symfony\Bundle\MakerBundle\Renderer\FormTypeRenderer;
2832
use Symfony\Bundle\MakerBundle\Security\InteractiveSecurityHelper;
2933
use Symfony\Bundle\MakerBundle\Security\Model\Authenticator;
@@ -68,6 +72,8 @@
6872
*/
6973
final class MakeRegistrationForm extends AbstractMaker
7074
{
75+
use CanGenerateTestsTrait;
76+
7177
private string $userClass;
7278
private string $usernameField;
7379
private string $passwordField;
@@ -104,6 +110,8 @@ public function configureCommand(Command $command, InputConfiguration $inputConf
104110
$command
105111
->setHelp(file_get_contents(__DIR__.'/../Resources/help/MakeRegistrationForm.txt'))
106112
;
113+
114+
$this->configureCommandWithTestsOption($command);
107115
}
108116

109117
public function interact(InputInterface $input, ConsoleStyle $io, Command $command): void
@@ -180,6 +188,8 @@ public function interact(InputInterface $input, ConsoleStyle $io, Command $comma
180188
$routeNames = array_keys($this->router->getRouteCollection()->all());
181189
$this->redirectRouteName = $io->choice('What route should the user be redirected to after registration?', $routeNames);
182190
}
191+
192+
$this->interactSetGenerateTests($input, $io);
183193
}
184194

185195
/** @param array<string, mixed> $securityData */
@@ -400,6 +410,35 @@ public function generate(InputInterface $input, ConsoleStyle $io, Generator $gen
400410
$this->fileManager->dumpFile($classDetails->getPath(), $userManipulator->getSourceCode());
401411
}
402412

413+
// Generate PHPUnit Tests
414+
if ($this->shouldGenerateTests()) {
415+
$testClassDetails = $generator->createClassNameDetails(
416+
'RegistrationControllerTest',
417+
'Test\\'
418+
);
419+
420+
$useStatements = new UseStatementGenerator([
421+
EntityManager::class,
422+
KernelBrowser::class,
423+
TemplatedEmail::class,
424+
WebTestCase::class,
425+
$userRepoVars['repository_full_class_name'],
426+
]);
427+
428+
$generator->generateFile(
429+
targetPath: sprintf('tests/%s.php', $testClassDetails->getShortName()),
430+
templateName: $this->willVerifyEmail ? 'registration/Test.WithVerify.tpl.php' : 'registration/Test.WithoutVerify.tpl.php',
431+
variables: array_merge([
432+
'use_statements' => $useStatements,
433+
'from_email' => $this->fromEmailAddress ?? null,
434+
], $userRepoVars)
435+
);
436+
437+
if (!class_exists(WebTestCase::class)) {
438+
$io->caution('You\'ll need to install the `symfony/test-pack` to execute the tests for your new controller.');
439+
}
440+
}
441+
403442
$generator->writeChanges();
404443

405444
$this->writeSuccessMessage($io);

src/Maker/MakeResetPassword.php

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,14 @@
1111

1212
namespace Symfony\Bundle\MakerBundle\Maker;
1313

14+
use Doctrine\ORM\EntityManager;
1415
use Doctrine\ORM\EntityManagerInterface;
1516
use PhpParser\Builder\Param;
1617
use Symfony\Bridge\Twig\AppVariable;
1718
use Symfony\Bridge\Twig\Mime\TemplatedEmail;
1819
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
20+
use Symfony\Bundle\FrameworkBundle\KernelBrowser;
21+
use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
1922
use Symfony\Bundle\MakerBundle\ConsoleStyle;
2023
use Symfony\Bundle\MakerBundle\DependencyBuilder;
2124
use Symfony\Bundle\MakerBundle\Doctrine\DoctrineHelper;
@@ -26,6 +29,7 @@
2629
use Symfony\Bundle\MakerBundle\FileManager;
2730
use Symfony\Bundle\MakerBundle\Generator;
2831
use Symfony\Bundle\MakerBundle\InputConfiguration;
32+
use Symfony\Bundle\MakerBundle\Maker\Common\CanGenerateTestsTrait;
2933
use Symfony\Bundle\MakerBundle\Maker\Common\UidTrait;
3034
use Symfony\Bundle\MakerBundle\Security\InteractiveSecurityHelper;
3135
use Symfony\Bundle\MakerBundle\Util\ClassNameDetails;
@@ -51,6 +55,8 @@
5155
use Symfony\Component\OptionsResolver\OptionsResolver;
5256
use Symfony\Component\PasswordHasher\Hasher\UserPasswordHasherInterface;
5357
use Symfony\Component\Routing\Attribute\Route;
58+
use Symfony\Component\Routing\Route as RouteObject;
59+
use Symfony\Component\Routing\RouterInterface;
5460
use Symfony\Component\Translation\Translator;
5561
use Symfony\Component\Validator\Constraints\Length;
5662
use Symfony\Component\Validator\Constraints\NotBlank;
@@ -81,11 +87,13 @@
8187
*/
8288
class MakeResetPassword extends AbstractMaker
8389
{
90+
use CanGenerateTestsTrait;
8491
use UidTrait;
8592

8693
private string $fromEmailAddress;
8794
private string $fromEmailName;
8895
private string $controllerResetSuccessRedirect;
96+
private ?RouteObject $controllerResetSuccessRoute = null;
8997
private string $userClass;
9098
private string $emailPropertyName;
9199
private string $emailGetterMethodName;
@@ -95,6 +103,7 @@ public function __construct(
95103
private FileManager $fileManager,
96104
private DoctrineHelper $doctrineHelper,
97105
private EntityClassGenerator $entityClassGenerator,
106+
private ?RouterInterface $router = null,
98107
) {
99108
}
100109

@@ -115,6 +124,7 @@ public function configureCommand(Command $command, InputConfiguration $inputConf
115124
;
116125

117126
$this->addWithUuidOption($command);
127+
$this->configureCommandWithTestsOption($command);
118128
}
119129

120130
public function configureDependencies(DependencyBuilder $dependencies): void
@@ -172,6 +182,10 @@ public function interact(InputInterface $input, ConsoleStyle $io, Command $comma
172182
Validator::notBlank(...)
173183
);
174184

185+
if ($this->router instanceof RouterInterface) {
186+
$this->controllerResetSuccessRoute = $this->router->getRouteCollection()->get($this->controllerResetSuccessRedirect);
187+
}
188+
175189
$io->section('- Email -');
176190
$emailText[] = 'These are used to generate the email code. Don\'t worry, you can change them in the code later!';
177191
$io->text($emailText);
@@ -187,6 +201,8 @@ public function interact(InputInterface $input, ConsoleStyle $io, Command $comma
187201
null,
188202
Validator::notBlank(...)
189203
);
204+
205+
$this->interactSetGenerateTests($input, $io);
190206
}
191207

192208
public function generate(InputInterface $input, ConsoleStyle $io, Generator $generator): void
@@ -334,6 +350,44 @@ public function generate(InputInterface $input, ConsoleStyle $io, Generator $gen
334350
'resetPassword/twig_reset.tpl.php'
335351
);
336352

353+
// Generate PHPUnit tests
354+
if ($this->shouldGenerateTests()) {
355+
$testClassDetails = $generator->createClassNameDetails(
356+
'ResetPasswordControllerTest',
357+
'Test\\',
358+
);
359+
360+
$userRepositoryDetails = $generator->createClassNameDetails(
361+
sprintf('%sRepository', $userClassNameDetails->getShortName()),
362+
'Repository\\'
363+
);
364+
365+
$useStatements = new UseStatementGenerator([
366+
$userClassNameDetails->getFullName(),
367+
$userRepositoryDetails->getFullName(),
368+
EntityManager::class,
369+
KernelBrowser::class,
370+
WebTestCase::class,
371+
UserPasswordHasherInterface::class,
372+
]);
373+
374+
$generator->generateFile(
375+
targetPath: sprintf('tests/%s.php', $testClassDetails->getShortName()),
376+
templateName: 'resetPassword/Test.ResetPasswordController.tpl.php',
377+
variables: [
378+
'use_statements' => $useStatements,
379+
'user_short_name' => $userClassNameDetails->getShortName(),
380+
'user_repo_short_name' => $userRepositoryDetails->getShortName(),
381+
'success_route_path' => null !== $this->controllerResetSuccessRoute ? $this->controllerResetSuccessRoute->getPath() : '/',
382+
'from_email' => $this->fromEmailAddress,
383+
],
384+
);
385+
386+
if (!class_exists(WebTestCase::class)) {
387+
$io->caution('You\'ll need to install the `symfony/test-pack` to execute the tests for your new controller.');
388+
}
389+
}
390+
337391
$generator->writeChanges();
338392

339393
$this->writeSuccessMessage($io);

src/Resources/config/makers.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,7 @@
8989
<argument type="service" id="maker.file_manager" />
9090
<argument type="service" id="maker.doctrine_helper" />
9191
<argument type="service" id="maker.entity_class_generator" />
92+
<argument type="service" id="router" on-invalid="ignore" />
9293
<tag name="maker.command" />
9394
</service>
9495

src/Resources/help/_WithTests.txt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
To generate tailored PHPUnit tests, simply call:
2+
3+
<info>php %command.full_name% --with-tests</info>
4+
5+
This will generate a unit test in <info>tests/</info> for you to review then use
6+
to test the new functionality of your app.

0 commit comments

Comments
 (0)