Skip to content
60 changes: 41 additions & 19 deletions src/Codeception/Lib/Connector/Yii2.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,20 +15,18 @@
use Symfony\Component\BrowserKit\CookieJar;
use Symfony\Component\BrowserKit\History;
use Symfony\Component\BrowserKit\Request as BrowserkitRequest;
use yii\web\Request as YiiRequest;
use Symfony\Component\BrowserKit\Response;
use Yii;
use yii\base\Component;
use yii\base\Event;
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\ErrorHandler;
use yii\web\IdentityInterface;
use yii\web\Request;
use yii\web\Request as YiiRequest;
use yii\web\Response as YiiResponse;
use yii\web\User;

Expand All @@ -40,7 +38,21 @@ class Yii2 extends Client
{
use Shared\PhpSuperGlobalsConverter;

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';


const array CLEAN_METHODS = [
self::CLEAN_RECREATE,
self::CLEAN_CLEAR,
self::CLEAN_FORCE_RECREATE,
Expand All @@ -50,51 +62,55 @@ 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.
*/
const CLEAN_RECREATE = 'recreate';
const string CLEAN_RECREATE = 'recreate';
/**
* Same as recreate but will not warn when behaviors / event handlers are lost.
*/
const CLEAN_FORCE_RECREATE = 'force_recreate';
const string CLEAN_FORCE_RECREATE = 'force_recreate';
/**
* Clean the response object by resetting specific properties via its' `clear()` method.
* This will keep behaviors / event handlers, but could inadvertently leave some changes intact.
* @see \yii\web\Response::clear()
*/
const CLEAN_CLEAR = 'clear';
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
*/
const CLEAN_MANUAL = 'manual';
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.
* It means you cannot make any modification to application state inside your app, since they will get discarded.
* @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
Expand All @@ -109,7 +125,7 @@ class Yii2 extends Client


/**
* @var list<BaseMessage>
* @var list<MessageInterface>
*/
private array $emails = [];

Expand Down Expand Up @@ -211,7 +227,7 @@ public function getInternalDomains(): array

/**
* @internal
* @return list<BaseMessage> List of sent emails
* @return list<MessageInterface> List of sent emails
*/
public function getEmails(): array
{
Expand Down Expand Up @@ -290,7 +306,13 @@ 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] = fn(MailEvent $event) => $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");
Expand Down Expand Up @@ -455,7 +477,7 @@ protected function mockMailer(array $config): array

$mailerConfig = [
'class' => TestMailer::class,
'callback' => function (BaseMessage $message): void {
'callback' => function (MessageInterface $message): void {
$this->emails[] = $message;
}
];
Expand Down
3 changes: 2 additions & 1 deletion src/Codeception/Lib/Connector/Yii2/TestMailer.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,11 @@

use Closure;
use yii\mail\BaseMailer;
use yii\symfonymailer\Message;

class TestMailer extends BaseMailer
{
public $messageClass = \yii\symfonymailer\Message::class;
public $messageClass = Message::class;

public Closure $callback;

Expand Down
91 changes: 49 additions & 42 deletions src/Codeception/Module/Yii2.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,14 +22,13 @@
use Symfony\Component\BrowserKit\History;
use Yii;
use yii\base\Security;
use yii\web\Application as WebApplication;
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\Application;
use yii\web\Application as WebApplication;
use yii\web\IdentityInterface;

/**
Expand Down Expand Up @@ -92,6 +91,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`:
*
Expand Down Expand Up @@ -175,48 +179,43 @@
*
* Maintainer: **samdark**
* Stability: **stable**
*
* @phpstan-type ClientConfig array{
* 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<string>,
* recreateApplication: bool,
* closeSessionOnRecreateApplication: bool,
* applicationClass: class-string<\yii\base\Application>|null,
* configFile: string
* }
* @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<string>,
* 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<string>,
* recreateApplication: bool,
* closeSessionOnRecreateApplication: bool,
* applicationClass: class-string<\yii\base\Application>|null
* }
*
* @phpstan-type ValidConfig array{
* fixturesMethod: string,
* cleanup: bool,
* ignoreCollidingDSN: bool,
* @phpstan-type ValidConfig (ModuleConfig & array{
* 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<string>,
* recreateApplication: bool,
* closeSessionOnRecreateApplication: bool,
* applicationClass: class-string<\yii\base\Application>|null
* }
* @phpstan-type SessionBackup array{cookie: array<mixed>, session: array<mixed>, headers: array<string, string>, clientContext: array{ cookieJar: CookieJar, history: History }}
* @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<string>,
* recreateApplication: bool,
* closeSessionOnRecreateApplication: bool,
* applicationClass: class-string<\yii\base\Application>|null
* }
* configFile: string
* })
* @phpstan-type SessionBackup array{
* cookie: array<mixed>,
* session: array<mixed>,
* headers: array<string, string>,
* clientContext: array{ cookieJar: CookieJar, history: History }
* }
*/
class Yii2 extends Framework implements ActiveRecord, MultiSession, PartedModule
{
Expand All @@ -241,6 +240,7 @@ class Yii2 extends Framework implements ActiveRecord, MultiSession, PartedModule
'requestCleanMethod' => Yii2Connector::CLEAN_RECREATE,
'recreateComponents' => [],
'recreateApplication' => false,
'mailMethod' => Yii2Connector::MAIL_CATCH,
'closeSessionOnRecreateApplication' => true,
'applicationClass' => null,
];
Expand Down Expand Up @@ -346,6 +346,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,
Expand All @@ -367,6 +373,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();
}

Expand Down Expand Up @@ -797,7 +804,7 @@ public function dontSeeEmailIsSent(): void
* ```
*
* @part email
* @return list<BaseMessage&MessageInterface> List of sent emails
* @return list<MessageInterface> List of sent emails
* @throws \Codeception\Exception\ModuleException
*/
public function grabSentEmails(): array
Expand All @@ -820,7 +827,7 @@ public function grabSentEmails(): array
* ```
* @part email
*/
public function grabLastSentEmail(): BaseMessage|null
public function grabLastSentEmail(): MessageInterface|null
{
$this->seeEmailIsSent();
$messages = $this->grabSentEmails();
Expand Down