diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index ce4877d..7e2667d 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -1092,12 +1092,6 @@ parameters: count: 1 path: tests/_support/FunctionalTester.php - - - message: '#^Method tests\\FunctionalTester\:\:createAndSetCsrfCookie\(\) should return array\ but returns mixed\.$#' - identifier: return.type - count: 1 - path: tests/_support/FunctionalTester.php - - message: '#^Method tests\\FunctionalTester\:\:dontSee\(\) has parameter \$selector with no value type specified in iterable type array\.$#' identifier: missingType.iterableValue @@ -1194,12 +1188,6 @@ parameters: count: 1 path: tests/_support/FunctionalTester.php - - - message: '#^Method tests\\FunctionalTester\:\:getInternalDomains\(\) should return non\-empty\-list\ but returns mixed\.$#' - identifier: return.type - count: 1 - path: tests/_support/FunctionalTester.php - - message: '#^Method tests\\FunctionalTester\:\:grabAttributeFrom\(\) has parameter \$cssOrXpath with no type specified\.$#' identifier: missingType.parameter @@ -1212,48 +1200,18 @@ parameters: count: 1 path: tests/_support/FunctionalTester.php - - - message: '#^Method tests\\FunctionalTester\:\:grabFixture\(\) should return yii\\db\\ActiveRecord\|yii\\test\\Fixture\|null but returns mixed\.$#' - identifier: return.type - count: 1 - path: tests/_support/FunctionalTester.php - - message: '#^Method tests\\FunctionalTester\:\:grabFixtures\(\) has invalid return type tests\\_generated\\Fixture\.$#' identifier: class.notFound count: 1 path: tests/_support/FunctionalTester.php - - - message: '#^Method tests\\FunctionalTester\:\:grabFixtures\(\) should return array\ but returns mixed\.$#' - identifier: return.type - count: 1 - path: tests/_support/FunctionalTester.php - - - - message: '#^Method tests\\FunctionalTester\:\:grabLastSentEmail\(\) should return yii\\mail\\BaseMessage\|null but returns mixed\.$#' - identifier: return.type - count: 1 - path: tests/_support/FunctionalTester.php - - message: '#^Method tests\\FunctionalTester\:\:grabMultiple\(\) has parameter \$cssOrXpath with no type specified\.$#' identifier: missingType.parameter count: 1 path: tests/_support/FunctionalTester.php - - - message: '#^Method tests\\FunctionalTester\:\:grabMultiple\(\) should return array\ but returns mixed\.$#' - identifier: return.type - count: 1 - path: tests/_support/FunctionalTester.php - - - - message: '#^Method tests\\FunctionalTester\:\:grabPageSource\(\) should return string but returns mixed\.$#' - identifier: return.type - count: 1 - path: tests/_support/FunctionalTester.php - - message: '#^Method tests\\FunctionalTester\:\:grabRecord\(\) has invalid return type tests\\_generated\\ActiveRecordInterface\.$#' identifier: class.notFound @@ -1266,30 +1224,12 @@ parameters: count: 1 path: tests/_support/FunctionalTester.php - - - message: '#^Method tests\\FunctionalTester\:\:grabRecord\(\) should return array\|yii\\db\\ActiveRecordInterface\|null but returns mixed\.$#' - identifier: return.type - count: 1 - path: tests/_support/FunctionalTester.php - - - - message: '#^Method tests\\FunctionalTester\:\:grabSentEmails\(\) has invalid return type tests\\_generated\\BaseMessage\.$#' - identifier: class.notFound - count: 1 - path: tests/_support/FunctionalTester.php - - message: '#^Method tests\\FunctionalTester\:\:grabSentEmails\(\) has invalid return type tests\\_generated\\MessageInterface\.$#' identifier: class.notFound count: 1 path: tests/_support/FunctionalTester.php - - - message: '#^Method tests\\FunctionalTester\:\:grabSentEmails\(\) should return list\ but returns mixed\.$#' - identifier: return.type - count: 1 - path: tests/_support/FunctionalTester.php - - message: '#^Method tests\\FunctionalTester\:\:grabTextFrom\(\) has parameter \$cssOrXPathOrRegex with no type specified\.$#' identifier: missingType.parameter diff --git a/src/Codeception/Lib/Connector/Yii2.php b/src/Codeception/Lib/Connector/Yii2.php index bd4b180..8be068c 100644 --- a/src/Codeception/Lib/Connector/Yii2.php +++ b/src/Codeception/Lib/Connector/Yii2.php @@ -21,7 +21,9 @@ use yii\base\ExitException; use yii\base\Security; use yii\base\UserException; -use yii\mail\BaseMessage; +use yii\mail\BaseMailer; +use yii\mail\MailEvent; +use yii\mail\MessageInterface; use yii\web\Application; use yii\web\IdentityInterface; use yii\web\Request as YiiRequest; @@ -36,7 +38,22 @@ final class Yii2 extends Client { use Shared\PhpSuperGlobalsConverter; - public const CLEAN_METHODS = [ + public const array MAIL_METHODS = [ + self::MAIL_CATCH, + self::MAIL_EVENT_AFTER, + self::MAIL_EVENT_BEFORE, + self::MAIL_IGNORE + ]; + + public const string MAIL_CATCH = 'catch'; + + public const string MAIL_EVENT_AFTER = 'after'; + + public const string MAIL_EVENT_BEFORE = 'before'; + + public const string MAIL_IGNORE = 'ignore'; + + public const array CLEAN_METHODS = [ self::CLEAN_RECREATE, self::CLEAN_CLEAR, self::CLEAN_FORCE_RECREATE, @@ -47,12 +64,12 @@ final class Yii2 extends Client * Clean the response object by recreating it. * This might lose behaviors / event handlers / other changes that are done in the application bootstrap phase. */ - public const CLEAN_RECREATE = 'recreate'; + public const string CLEAN_RECREATE = 'recreate'; /** * Same as recreate but will not warn when behaviors / event handlers are lost. */ - public const CLEAN_FORCE_RECREATE = 'force_recreate'; + public const string CLEAN_FORCE_RECREATE = 'force_recreate'; /** * Clean the response object by resetting specific properties via its' `clear()` method. @@ -60,33 +77,38 @@ final class Yii2 extends Client * * @see \yii\web\Response::clear() */ - public const CLEAN_CLEAR = 'clear'; + public const string CLEAN_CLEAR = 'clear'; /** * Do not clean the response, instead the test writer will be responsible for manually resetting the response in * between requests during one test */ - public const CLEAN_MANUAL = 'manual'; + public const string CLEAN_MANUAL = 'manual'; /** * @var string application config file */ - public $configFile; + public string $configFile; + + /** + * @var self::MAIL_CATCH|self::MAIL_IGNORE|self::MAIL_EVENT_AFTER|self::MAIL_EVENT_BEFORE method for handling mails + */ + public string $mailMethod; /** * @var string method for cleaning the response object before each request */ - public $responseCleanMethod; + public string $responseCleanMethod; /** * @var string method for cleaning the request object before each request */ - public $requestCleanMethod; + public string $requestCleanMethod; /** * @var string[] List of component names that must be recreated before each request */ - public $recreateComponents = []; + public array $recreateComponents = []; /** * This option is there primarily for backwards compatibility. @@ -94,7 +116,7 @@ final class Yii2 extends Client * * @var bool whether to recreate the whole application before each request */ - public $recreateApplication = false; + public bool $recreateApplication = false; /** * @var bool whether to close the session in between requests inside a single test, if recreateApplication is set to true @@ -108,7 +130,7 @@ final class Yii2 extends Client public string|null $applicationClass = null; /** - * @var list + * @var list */ private array $emails = []; @@ -213,7 +235,7 @@ public function getInternalDomains(): array /** * @internal - * @return list List of sent emails + * @return list List of sent emails */ public function getEmails(): array { @@ -281,7 +303,17 @@ public function startApp(?\yii\log\Logger $logger = null): void unset($config['container']); } - $config = $this->mockMailer($config); + match ($this->mailMethod) { + self::MAIL_CATCH => $config = $this->mockMailer($config), + self::MAIL_EVENT_AFTER => $config['components']['mailer']['on ' . BaseMailer::EVENT_AFTER_SEND] = function (MailEvent $event): void { + if ($event->isSuccessful) { + $this->emails[] = $event->message; + } + }, + self::MAIL_EVENT_BEFORE => $config['components']['mailer']['on ' . BaseMailer::EVENT_BEFORE_SEND] = fn (MailEvent $event) => $this->emails[] = $event->message, + self::MAIL_IGNORE => null// Do nothing + }; + $app = Yii::createObject($config); if (! $app instanceof \yii\base\Application) { throw new ModuleConfigException($this, "Failed to initialize Yii2 app"); @@ -450,7 +482,7 @@ protected function mockMailer(array $config): array $mailerConfig = [ 'class' => TestMailer::class, - 'callback' => function (BaseMessage $message): void { + 'callback' => function (MessageInterface $message): void { $this->emails[] = $message; }, ]; diff --git a/src/Codeception/Lib/Connector/Yii2/TestMailer.php b/src/Codeception/Lib/Connector/Yii2/TestMailer.php index 89b9bca..78d8908 100644 --- a/src/Codeception/Lib/Connector/Yii2/TestMailer.php +++ b/src/Codeception/Lib/Connector/Yii2/TestMailer.php @@ -6,10 +6,11 @@ use Closure; use yii\mail\BaseMailer; +use yii\symfonymailer\Message; final class TestMailer extends BaseMailer { - public $messageClass = \yii\symfonymailer\Message::class; + public $messageClass = Message::class; public Closure $callback; diff --git a/src/Codeception/Module/Yii2.php b/src/Codeception/Module/Yii2.php index 0da51e5..4abfb65 100644 --- a/src/Codeception/Module/Yii2.php +++ b/src/Codeception/Module/Yii2.php @@ -23,7 +23,6 @@ use yii\db\ActiveQueryInterface; use yii\db\ActiveRecordInterface; use yii\helpers\Url; -use yii\mail\BaseMessage; use yii\mail\MessageInterface; use yii\test\Fixture; use yii\web\IdentityInterface; @@ -88,6 +87,11 @@ * changes will get discarded. * * `recreateApplication` - (default: `false`) whether to recreate the whole * application before each request + * * `mailMethod` - (default: `catch`) Method for handling email via the 'mailer' + * component. `ignore` will not do anything with mail, this means mails are not + * inspectable by the test runner, using `before` or `after` will use mailer + * events; making the mails inspectable but also allowing your default mail + * handling to work * * You can use this module by setting params in your `functional.suite.yml`: * @@ -173,45 +177,37 @@ * Stability: **stable** * * @phpstan-type ModuleConfig array{ - * fixturesMethod: string, - * cleanup: bool, - * ignoreCollidingDSN: bool, - * transaction: bool|null, - * entryScript: string, - * entryUrl: string, - * configFile: string|null, - * responseCleanMethod: Yii2Connector::CLEAN_CLEAR|Yii2Connector::CLEAN_MANUAL|Yii2Connector::CLEAN_RECREATE, - * requestCleanMethod: Yii2Connector::CLEAN_CLEAR|Yii2Connector::CLEAN_MANUAL|Yii2Connector::CLEAN_RECREATE, - * recreateComponents: list, - * recreateApplication: bool, - * closeSessionOnRecreateApplication: bool, - * applicationClass: class-string<\yii\base\Application>|null - * } - * - * @phpstan-type ValidConfig array{ - * fixturesMethod: string, - * cleanup: bool, - * ignoreCollidingDSN: bool, - * transaction: bool|null, - * entryScript: string, - * entryUrl: string, - * configFile: string, - * responseCleanMethod: Yii2Connector::CLEAN_CLEAR|Yii2Connector::CLEAN_MANUAL|Yii2Connector::CLEAN_RECREATE, - * requestCleanMethod: Yii2Connector::CLEAN_CLEAR|Yii2Connector::CLEAN_MANUAL|Yii2Connector::CLEAN_RECREATE, - * recreateComponents: list, - * recreateApplication: bool, - * closeSessionOnRecreateApplication: bool, - * applicationClass: class-string<\yii\base\Application>|null + * configFile: string|null, + * fixturesMethod: string, + * cleanup: bool, + * ignoreCollidingDSN: bool, + * transaction: bool|null, + * entryScript: string, + * entryUrl: string, + * responseCleanMethod: Yii2Connector::CLEAN_CLEAR|Yii2Connector::CLEAN_MANUAL|Yii2Connector::CLEAN_RECREATE, + * requestCleanMethod: Yii2Connector::CLEAN_CLEAR|Yii2Connector::CLEAN_MANUAL|Yii2Connector::CLEAN_RECREATE, + * mailMethod: Yii2Connector::MAIL_CATCH|Yii2Connector::MAIL_IGNORE|Yii2Connector::MAIL_EVENT_AFTER|Yii2Connector::MAIL_EVENT_BEFORE, + * recreateComponents: list, + * recreateApplication: bool, + * closeSessionOnRecreateApplication: bool, + * applicationClass: class-string<\yii\base\Application>|null * } + * * @phpstan-type ClientConfig array{ - * configFile: string, - * responseCleanMethod: Yii2Connector::CLEAN_CLEAR|Yii2Connector::CLEAN_MANUAL|Yii2Connector::CLEAN_RECREATE, - * requestCleanMethod: Yii2Connector::CLEAN_CLEAR|Yii2Connector::CLEAN_MANUAL|Yii2Connector::CLEAN_RECREATE, - * recreateComponents: list, - * recreateApplication: bool, - * closeSessionOnRecreateApplication: bool, - * applicationClass: class-string<\yii\base\Application>|null - * } + * configFile: string, + * responseCleanMethod: Yii2Connector::CLEAN_CLEAR|Yii2Connector::CLEAN_MANUAL|Yii2Connector::CLEAN_RECREATE, + * requestCleanMethod: Yii2Connector::CLEAN_CLEAR|Yii2Connector::CLEAN_MANUAL|Yii2Connector::CLEAN_RECREATE, + * mailMethod: Yii2Connector::MAIL_CATCH|Yii2Connector::MAIL_IGNORE|Yii2Connector::MAIL_EVENT_AFTER|Yii2Connector::MAIL_EVENT_BEFORE, + * recreateComponents: list, + * recreateApplication: bool, + * closeSessionOnRecreateApplication: bool, + * applicationClass: class-string<\yii\base\Application>|null + * } + * + * @phpstan-type ValidConfig (ModuleConfig & array{ + * transaction: bool|null, + * configFile: string + * }) */ final class Yii2 extends Framework implements ActiveRecord, PartedModule { @@ -237,6 +233,7 @@ final class Yii2 extends Framework implements ActiveRecord, PartedModule 'requestCleanMethod' => Yii2Connector::CLEAN_RECREATE, 'recreateComponents' => [], 'recreateApplication' => false, + 'mailMethod' => Yii2Connector::MAIL_CATCH, 'closeSessionOnRecreateApplication' => true, 'applicationClass' => null, ]; @@ -345,6 +342,12 @@ protected function validateConfig(): void "The response clean method must be one of: " . $validMethods, ); } + if (! in_array($this->config['mailMethod'], Yii2Connector::MAIL_METHODS, true)) { + throw new ModuleConfigException( + self::class, + "The mail method must be one of: " . $validMethods + ); + } if (! in_array($this->config['requestCleanMethod'], Yii2Connector::CLEAN_METHODS, true)) { throw new ModuleConfigException( self::class, @@ -365,6 +368,7 @@ private function configureClient(array $settings): void $client->recreateApplication = $settings['recreateApplication']; $client->closeSessionOnRecreateApplication = $settings['closeSessionOnRecreateApplication']; $client->applicationClass = $settings['applicationClass']; + $client->mailMethod = $settings['mailMethod']; $client->resetApplication(); } @@ -799,7 +803,7 @@ public function dontSeeEmailIsSent(): void * ``` * * @part email - * @return list List of sent emails + * @return list List of sent emails * @throws \Codeception\Exception\ModuleException */ public function grabSentEmails(): array @@ -823,7 +827,7 @@ public function grabSentEmails(): array * * @part email */ - public function grabLastSentEmail(): BaseMessage|null + public function grabLastSentEmail(): MessageInterface|null { $this->seeEmailIsSent(); $messages = $this->grabSentEmails();