diff --git a/api/mails/new-email-confirmation-text.php b/api/mails/new-email-confirmation-text.php deleted file mode 100644 index f516746..0000000 --- a/api/mails/new-email-confirmation-text.php +++ /dev/null @@ -1,12 +0,0 @@ - - -This E-mail was specified as new for account username ?>. To confirm this E-mail, pass code below into form on site. - -Code: - -// P.S. yes, this is E-mail is not designed yet :) diff --git a/api/models/authentication/ForgotPasswordForm.php b/api/models/authentication/ForgotPasswordForm.php index 051decb..1c0b0d3 100644 --- a/api/models/authentication/ForgotPasswordForm.php +++ b/api/models/authentication/ForgotPasswordForm.php @@ -1,6 +1,7 @@ sendMail($emailActivation); + EmailHelper::forgotPassword($emailActivation); return true; } - public function sendMail(EmailActivation $emailActivation) { - /** @var \yii\swiftmailer\Mailer $mailer */ - $mailer = Yii::$app->mailer; - $fromEmail = Yii::$app->params['fromEmail']; - if (!$fromEmail) { - throw new InvalidConfigException('Please specify fromEmail app in app params'); - } - - $account = $emailActivation->account; - $htmlBody = Yii::$app->emailRenderer->getTemplate('forgotPassword') - ->setLocale($account->lang) - ->setParams([ - 'username' => $account->username, - 'code' => $emailActivation->key, - 'link' => Yii::$app->request->getHostInfo() . '/recover-password/' . $emailActivation->key, - ]) - ->render(); - - /** @var \yii\swiftmailer\Message $message */ - $message = $mailer->compose() - ->setHtmlBody($htmlBody) - ->setTo([$account->email => $account->username]) - ->setFrom([$fromEmail => 'Ely.by Accounts']) - ->setSubject('Ely.by Account forgot password'); - - if (!$message->send()) { - throw new ErrorException('Unable send email with activation code.'); - } - } - public function getLogin() { return $this->login; } diff --git a/api/models/authentication/RegistrationForm.php b/api/models/authentication/RegistrationForm.php index c39a249..40fb293 100644 --- a/api/models/authentication/RegistrationForm.php +++ b/api/models/authentication/RegistrationForm.php @@ -2,12 +2,12 @@ namespace api\models\authentication; use api\components\ReCaptcha\Validator as ReCaptchaValidator; +use common\emails\EmailHelper; use api\models\base\ApiForm; use common\helpers\Error as E; use common\components\UserFriendlyRandomKey; use common\models\Account; use common\models\confirmations\RegistrationConfirmation; -use common\models\EmailActivation; use common\models\UsernameHistory; use common\validators\EmailValidator; use common\validators\LanguageValidator; @@ -17,7 +17,6 @@ use Exception; use Ramsey\Uuid\Uuid; use Yii; use yii\base\ErrorException; -use yii\base\InvalidConfigException; use yii\helpers\ArrayHelper; use const common\LATEST_RULES_VERSION; @@ -103,7 +102,7 @@ class RegistrationForm extends ApiForm { throw new ErrorException('Cannot save username history record'); } - $this->sendMail($emailActivation, $account); + EmailHelper::registration($emailActivation); $transaction->commit(); } catch (Exception $e) { @@ -114,37 +113,6 @@ class RegistrationForm extends ApiForm { return $account; } - // TODO: подумать, чтобы вынести этот метод в какую-то отдельную конструкцию, т.к. используется и внутри NewAccountActivationForm - public function sendMail(EmailActivation $emailActivation, Account $account) { - /** @var \yii\swiftmailer\Mailer $mailer */ - $mailer = Yii::$app->mailer; - $fromEmail = Yii::$app->params['fromEmail']; - - if (!$fromEmail) { - throw new InvalidConfigException('Please specify fromEmail app in app params'); - } - - $htmlBody = Yii::$app->emailRenderer->getTemplate('register') - ->setLocale($account->lang) - ->setParams([ - 'username' => $account->username, - 'code' => $emailActivation->key, - 'link' => Yii::$app->request->getHostInfo() . '/activation/' . $emailActivation->key, - ]) - ->render(); - - /** @var \yii\swiftmailer\Message $message */ - $message = $mailer->compose() - ->setHtmlBody($htmlBody) - ->setTo([$account->email => $account->username]) - ->setFrom([$fromEmail => 'Ely.by Accounts']) - ->setSubject('Ely.by Account registration'); - - if (!$message->send()) { - throw new ErrorException('Unable send email with activation code.'); - } - } - /** * Метод проверяет, можно ли занять указанный при регистрации ник или e-mail. Так случается, * что пользователи вводят неправильный e-mail или ник, после замечают это и пытаются вновь diff --git a/api/models/authentication/RepeatAccountActivationForm.php b/api/models/authentication/RepeatAccountActivationForm.php index 6041f33..a687052 100644 --- a/api/models/authentication/RepeatAccountActivationForm.php +++ b/api/models/authentication/RepeatAccountActivationForm.php @@ -2,6 +2,7 @@ namespace api\models\authentication; use api\components\ReCaptcha\Validator as ReCaptchaValidator; +use common\emails\EmailHelper; use api\models\base\ApiForm; use common\helpers\Error as E; use common\components\UserFriendlyRandomKey; @@ -72,8 +73,7 @@ class RepeatAccountActivationForm extends ApiForm { throw new ErrorException('Unable save email-activation model.'); } - $regForm = new RegistrationForm(); - $regForm->sendMail($activation, $account); + EmailHelper::registration($activation); $transaction->commit(); } catch (ErrorException $e) { diff --git a/api/models/profile/ChangeEmail/InitStateForm.php b/api/models/profile/ChangeEmail/InitStateForm.php index fd14bba..217415f 100644 --- a/api/models/profile/ChangeEmail/InitStateForm.php +++ b/api/models/profile/ChangeEmail/InitStateForm.php @@ -1,6 +1,7 @@ removeOldCode(); $activation = $this->createCode(); - $this->sendCode($activation); + + EmailHelper::changeEmailConfirmCurrent($activation); $transaction->commit(); } catch (Exception $e) { @@ -93,29 +94,6 @@ class InitStateForm extends ApiForm { $emailActivation->delete(); } - public function sendCode(EmailActivation $code) { - $mailer = Yii::$app->mailer; - $fromEmail = Yii::$app->params['fromEmail']; - if (!$fromEmail) { - throw new InvalidConfigException('Please specify fromEmail app in app params'); - } - - $acceptor = $code->account; - $message = $mailer->compose([ - 'html' => '@app/mails/current-email-confirmation-html', - 'text' => '@app/mails/current-email-confirmation-text', - ], [ - 'key' => $code->key, - ]) - ->setTo([$acceptor->email => $acceptor->username]) - ->setFrom([$fromEmail => 'Ely.by Accounts']) - ->setSubject('Ely.by Account change E-mail confirmation'); - - if (!$message->send()) { - throw new ErrorException('Unable send email with activation code.'); - } - } - /** * Возвращает E-mail активацию, которая использовалась внутри процесса для перехода на следующий шаг. * Метод предназначен для проверки, не слишком ли часто отправляются письма о смене E-mail. diff --git a/api/models/profile/ChangeEmail/NewEmailForm.php b/api/models/profile/ChangeEmail/NewEmailForm.php index 9f05948..ba67dfc 100644 --- a/api/models/profile/ChangeEmail/NewEmailForm.php +++ b/api/models/profile/ChangeEmail/NewEmailForm.php @@ -1,6 +1,7 @@ delete(); $activation = $this->createCode(); - $this->sendCode($activation); + + EmailHelper::changeEmailConfirmNew($activation); $transaction->commit(); @@ -67,32 +68,6 @@ class NewEmailForm extends ApiForm { return $emailActivation; } - public function sendCode(EmailActivation $code) { - /** @var \yii\swiftmailer\Mailer $mailer */ - $mailer = Yii::$app->mailer; - $fromEmail = Yii::$app->params['fromEmail']; - if (!$fromEmail) { - throw new InvalidConfigException('Please specify fromEmail app in app params'); - } - - $acceptor = $code->account; - /** @var \yii\swiftmailer\Message $message */ - $message = $mailer->compose([ - 'html' => '@app/mails/new-email-confirmation-html', - 'text' => '@app/mails/new-email-confirmation-text', - ], [ - 'key' => $code->key, - 'account' => $acceptor, - ]) - ->setTo([$this->email => $acceptor->username]) - ->setFrom([$fromEmail => 'Ely.by Accounts']) - ->setSubject('Ely.by Account new E-mail confirmation'); - - if (!$message->send()) { - throw new ErrorException('Unable send email with activation code.'); - } - } - public function __construct(Account $account, array $config = []) { $this->account = $account; parent::__construct($config); diff --git a/common/emails/EmailHelper.php b/common/emails/EmailHelper.php new file mode 100644 index 0000000..2d40cb3 --- /dev/null +++ b/common/emails/EmailHelper.php @@ -0,0 +1,56 @@ +account; + $locale = $account->lang; + $params = new RegistrationEmailParams( + $account->username, + $emailActivation->key, + Yii::$app->request->getHostInfo() . '/activation/' . $emailActivation->key + ); + + (new RegistrationEmail(self::buildTo($account), $locale, $params))->send(); + } + + public static function forgotPassword(ForgotPassword $emailActivation): void { + $account = $emailActivation->account; + $locale = $account->lang; + $params = new ForgotPasswordParams( + $account->username, + $emailActivation->key, + Yii::$app->request->getHostInfo() . '/recover-password/' . $emailActivation->key + ); + + (new ForgotPasswordEmail(self::buildTo($account), $locale, $params))->send(); + } + + public static function changeEmailConfirmCurrent(CurrentEmailConfirmation $emailActivation): void { + (new ChangeEmailConfirmCurrentEmail(self::buildTo($emailActivation->account), $emailActivation->key))->send(); + } + + public static function changeEmailConfirmNew(NewEmailConfirmation $emailActivation): void { + $account = $emailActivation->account; + (new ChangeEmailConfirmNewEmail(self::buildTo($account), $account->username, $emailActivation->key))->send(); + } + + public static function buildTo(Account $account): array { + return [$account->email => $account->username]; + } + +} diff --git a/common/emails/Template.php b/common/emails/Template.php new file mode 100644 index 0000000..6637421 --- /dev/null +++ b/common/emails/Template.php @@ -0,0 +1,79 @@ + name] + */ + public function __construct($to) { + $this->mailer = Yii::$app->mailer; + $this->to = $to; + } + + /** + * @return array|string + */ + public function getTo() { + return $this->to; + } + + abstract public function getSubject(): string; + + /** + * @return array|string + * @throws InvalidConfigException + */ + public function getFrom() { + $fromEmail = Yii::$app->params['fromEmail']; + if (!$fromEmail) { + throw new InvalidConfigException('Please specify fromEmail app in app params'); + } + + return [$fromEmail => 'Ely.by Accounts']; + } + + public function getParams(): array { + return []; + } + + public function getMailer(): MailerInterface { + return $this->mailer; + } + + public function send(): void { + if (!$this->createMessage()->send()) { + throw new CannotSendEmailException('Unable send email.'); + } + } + + /** + * @return string|array + */ + abstract protected function getView(); + + protected function createMessage(): MessageInterface { + return $this->getMailer() + ->compose($this->getView(), $this->getParams()) + ->setTo($this->getTo()) + ->setFrom($this->getFrom()) + ->setSubject($this->getSubject()); + } + +} diff --git a/common/emails/TemplateWithRenderer.php b/common/emails/TemplateWithRenderer.php new file mode 100644 index 0000000..33b40f1 --- /dev/null +++ b/common/emails/TemplateWithRenderer.php @@ -0,0 +1,66 @@ +emailRenderer = Yii::$app->emailRenderer; + $this->locale = $locale; + } + + public function getLocale(): string { + return $this->locale; + } + + public function getEmailRenderer(): EmailRenderer { + return $this->emailRenderer; + } + + /** + * Метод должен возвращать имя шаблона, который должен быть использован. + * Имена можно взять в репозитории elyby/email-renderer + * + * @return string + */ + abstract public function getTemplateName(): string; + + protected final function getView() { + return $this->getTemplateName(); + } + + protected function createMessage(): MessageInterface { + return $this->getMailer() + ->compose() + ->setHtmlBody($this->render()) + ->setTo($this->getTo()) + ->setFrom($this->getFrom()) + ->setSubject($this->getSubject()); + } + + private function render(): string { + return $this->getEmailRenderer() + ->getTemplate($this->getTemplateName()) + ->setLocale($this->getLocale()) + ->setParams($this->getParams()) + ->render(); + } + +} diff --git a/common/emails/exceptions/CannotSendEmailException.php b/common/emails/exceptions/CannotSendEmailException.php new file mode 100644 index 0000000..f893d22 --- /dev/null +++ b/common/emails/exceptions/CannotSendEmailException.php @@ -0,0 +1,8 @@ +key = $key; + } + + public function getSubject(): string { + return 'Ely.by Account change E-mail confirmation'; + } + + /** + * @return string|array + */ + protected function getView() { + return [ + 'html' => '@common/emails/views/current-email-confirmation-html', + 'text' => '@common/emails/views/current-email-confirmation-text', + ]; + } + + public function getParams(): array { + return [ + 'key' => $this->key, + ]; + } + +} diff --git a/common/emails/templates/ChangeEmailConfirmNewEmail.php b/common/emails/templates/ChangeEmailConfirmNewEmail.php new file mode 100644 index 0000000..507b830 --- /dev/null +++ b/common/emails/templates/ChangeEmailConfirmNewEmail.php @@ -0,0 +1,39 @@ +username = $username; + $this->key = $key; + } + + public function getSubject(): string { + return 'Ely.by Account new E-mail confirmation'; + } + + /** + * @return string|array + */ + protected function getView() { + return [ + 'html' => '@common/emails/views/new-email-confirmation-html', + 'text' => '@common/emails/views/new-email-confirmation-text', + ]; + } + + public function getParams(): array { + return [ + 'key' => $this->key, + 'username' => $this->username, + ]; + } + +} diff --git a/common/emails/templates/ForgotPasswordEmail.php b/common/emails/templates/ForgotPasswordEmail.php new file mode 100644 index 0000000..38def4d --- /dev/null +++ b/common/emails/templates/ForgotPasswordEmail.php @@ -0,0 +1,34 @@ +params = $params; + } + + public function getSubject(): string { + return 'Ely.by Account forgot password'; + } + + public function getTemplateName(): string { + return 'forgotPassword'; + } + + public function getParams(): array { + return [ + 'username' => $this->params->getUsername(), + 'code' => $this->params->getCode(), + 'link' => $this->params->getLink(), + ]; + } + +} diff --git a/common/emails/templates/ForgotPasswordParams.php b/common/emails/templates/ForgotPasswordParams.php new file mode 100644 index 0000000..c6dc6c1 --- /dev/null +++ b/common/emails/templates/ForgotPasswordParams.php @@ -0,0 +1,30 @@ +username = $username; + $this->code = $code; + $this->link = $code; + } + + public function getUsername(): string { + return $this->username; + } + + public function getCode(): string { + return $this->code; + } + + public function getLink(): string { + return $this->link; + } + +} diff --git a/common/emails/templates/RegistrationEmail.php b/common/emails/templates/RegistrationEmail.php new file mode 100644 index 0000000..bf85617 --- /dev/null +++ b/common/emails/templates/RegistrationEmail.php @@ -0,0 +1,34 @@ +params = $params; + } + + public function getSubject(): string { + return 'Ely.by Account registration'; + } + + public function getTemplateName(): string { + return 'register'; + } + + public function getParams(): array { + return [ + 'username' => $this->params->getUsername(), + 'code' => $this->params->getCode(), + 'link' => $this->params->getLink(), + ]; + } + +} diff --git a/common/emails/templates/RegistrationEmailParams.php b/common/emails/templates/RegistrationEmailParams.php new file mode 100644 index 0000000..b1c324c --- /dev/null +++ b/common/emails/templates/RegistrationEmailParams.php @@ -0,0 +1,30 @@ +username = $username; + $this->code = $code; + $this->link = $code; + } + + public function getUsername(): string { + return $this->username; + } + + public function getCode(): string { + return $this->code; + } + + public function getLink(): string { + return $this->link; + } + +} diff --git a/api/mails/current-email-confirmation-html.php b/common/emails/views/current-email-confirmation-html.php similarity index 100% rename from api/mails/current-email-confirmation-html.php rename to common/emails/views/current-email-confirmation-html.php diff --git a/api/mails/current-email-confirmation-text.php b/common/emails/views/current-email-confirmation-text.php similarity index 100% rename from api/mails/current-email-confirmation-text.php rename to common/emails/views/current-email-confirmation-text.php diff --git a/api/mails/feedback.php b/common/emails/views/feedback.php similarity index 100% rename from api/mails/feedback.php rename to common/emails/views/feedback.php diff --git a/api/mails/new-email-confirmation-html.php b/common/emails/views/new-email-confirmation-html.php similarity index 53% rename from api/mails/new-email-confirmation-html.php rename to common/emails/views/new-email-confirmation-html.php index 33b9a04..e1adab5 100644 --- a/api/mails/new-email-confirmation-html.php +++ b/common/emails/views/new-email-confirmation-html.php @@ -1,12 +1,12 @@

- This E-mail was specified as new for account username ?>. To confirm this E-mail, pass code + This E-mail was specified as new for account . To confirm this E-mail, pass code below into form on site.

Code:

diff --git a/common/emails/views/new-email-confirmation-text.php b/common/emails/views/new-email-confirmation-text.php new file mode 100644 index 0000000..100dd49 --- /dev/null +++ b/common/emails/views/new-email-confirmation-text.php @@ -0,0 +1,12 @@ + + +This E-mail was specified as new for account . To confirm this E-mail, pass code below into form on site. + +Code: + +// P.S. yes, this is E-mail is not designed yet :) diff --git a/composer.json b/composer.json index 3faccdc..b47cae3 100644 --- a/composer.json +++ b/composer.json @@ -40,7 +40,8 @@ "codeception/codeception": "~2.2.4", "codeception/specify": "*", "codeception/verify": "*", - "phploc/phploc": "^3.0.1" + "phploc/phploc": "^3.0.1", + "mockery/mockery": "1.0.0-alpha1" }, "config": { "process-timeout": 1800 diff --git a/tests/codeception/api/unit.suite.yml b/tests/codeception/api/unit.suite.yml index 3d31363..e79108b 100644 --- a/tests/codeception/api/unit.suite.yml +++ b/tests/codeception/api/unit.suite.yml @@ -4,6 +4,7 @@ modules: - Yii2: part: [orm, email, fixtures] - tests\codeception\common\_support\amqp\Helper + - tests\codeception\common\_support\Mockery config: Yii2: configFile: '../config/api/unit.php' diff --git a/tests/codeception/api/unit/TestCase.php b/tests/codeception/api/unit/TestCase.php index 1d1cf88..91b79ad 100644 --- a/tests/codeception/api/unit/TestCase.php +++ b/tests/codeception/api/unit/TestCase.php @@ -1,6 +1,8 @@ assert_mocks) { + \Mockery::close(); + } else { + \Mockery::getContainer()->mockery_close(); + \Mockery::resetContainer(); + } + } + + public function _failed(TestInterface $test, $fail) { + $this->assert_mocks = false; + } + +} diff --git a/tests/codeception/common/unit.suite.yml b/tests/codeception/common/unit.suite.yml index 98fb59d..80ce5da 100644 --- a/tests/codeception/common/unit.suite.yml +++ b/tests/codeception/common/unit.suite.yml @@ -3,6 +3,7 @@ modules: enabled: - Yii2: part: [orm, email, fixtures] + - tests\codeception\common\_support\Mockery config: Yii2: configFile: '../config/common/unit.php' diff --git a/tests/codeception/common/unit/TestCase.php b/tests/codeception/common/unit/TestCase.php index ef1be1f..b3c1f9b 100644 --- a/tests/codeception/common/unit/TestCase.php +++ b/tests/codeception/common/unit/TestCase.php @@ -1,6 +1,8 @@ makePartial(); + $account->username = 'mock-username'; + $account->email = 'mock@ely.by'; + $this->assertEquals(['mock@ely.by' => 'mock-username'], EmailHelper::buildTo($account)); + } + +} diff --git a/tests/codeception/common/unit/emails/TemplateTest.php b/tests/codeception/common/unit/emails/TemplateTest.php new file mode 100644 index 0000000..d796b32 --- /dev/null +++ b/tests/codeception/common/unit/emails/TemplateTest.php @@ -0,0 +1,47 @@ +makePartial(); + $this->assertEquals('find-me', $template->getTo()); + $this->assertInstanceOf(MailerInterface::class, $template->getMailer()); + } + + public function testGetFrom() { + Yii::$app->params['fromEmail'] = 'find-me'; + /** @var Template|\Mockery\MockInterface $template */ + $template = mock(Template::class)->makePartial(); + $this->assertEquals(['find-me' => 'Ely.by Accounts'], $template->getFrom()); + } + + public function testGetParams() { + /** @var Template|\Mockery\MockInterface $template */ + $template = mock(Template::class)->makePartial(); + $this->assertEquals([], $template->getParams()); + } + + public function testCreateMessage() { + Yii::$app->params['fromEmail'] = 'from@ely.by'; + /** @var Template|\Mockery\MockInterface $template */ + $template = mock(Template::class, [['to@ely.by' => 'To']])->makePartial(); + $template->shouldReceive('getSubject')->andReturn('mock-subject'); + /** @var MessageInterface $message */ + $message = $this->callProtected($template, 'createMessage'); + $this->assertInstanceOf(MessageInterface::class, $message); + $this->assertEquals(['to@ely.by' => 'To'], $message->getTo()); + $this->assertEquals(['from@ely.by' => 'Ely.by Accounts'], $message->getFrom()); + $this->assertEquals('mock-subject', $message->getSubject()); + } + +} diff --git a/tests/codeception/common/unit/emails/TemplateWithRendererTest.php b/tests/codeception/common/unit/emails/TemplateWithRendererTest.php new file mode 100644 index 0000000..83f715c --- /dev/null +++ b/tests/codeception/common/unit/emails/TemplateWithRendererTest.php @@ -0,0 +1,49 @@ +makePartial(); + $this->assertEquals('mock-to', $template->getTo()); + $this->assertEquals('mock-locale', $template->getLocale()); + $this->assertInstanceOf(MailerInterface::class, $template->getMailer()); + $this->assertInstanceOf(EmailRenderer::class, $template->getEmailRenderer()); + } + + public function testCreateMessage() { + /** @var TemplateBuilder|\Mockery\MockInterface $templateBuilder */ + $templateBuilder = mock(TemplateBuilder::class)->makePartial(); + $templateBuilder->shouldReceive('render')->andReturn('mock-html'); + + /** @var EmailRenderer|\Mockery\MockInterface $renderer */ + $renderer = mock(EmailRenderer::class)->makePartial(); + $renderer->shouldReceive('getTemplate')->with('mock-template')->andReturn($templateBuilder); + + /** @var TemplateWithRenderer|\Mockery\MockInterface $template */ + $template = mock(TemplateWithRenderer::class, [['to@ely.by' => 'To'], 'mock-locale']); + $template->makePartial(); + $template->shouldReceive('getEmailRenderer')->andReturn($renderer); + $template->shouldReceive('getFrom')->andReturn(['from@ely.by' => 'From']); + $template->shouldReceive('getSubject')->andReturn('mock-subject'); + $template->shouldReceive('getTemplateName')->andReturn('mock-template'); + /** @var \yii\swiftmailer\Message $message */ + $message = $this->callProtected($template, 'createMessage'); + $this->assertInstanceOf(MessageInterface::class, $message); + $this->assertEquals(['to@ely.by' => 'To'], $message->getTo()); + $this->assertEquals(['from@ely.by' => 'From'], $message->getFrom()); + $this->assertEquals('mock-subject', $message->getSubject()); + $this->assertEquals('mock-html', $message->getSwiftMessage()->getBody()); + } + +} diff --git a/tests/codeception/console/unit.suite.yml b/tests/codeception/console/unit.suite.yml index bdcb10b..9b3ddd9 100644 --- a/tests/codeception/console/unit.suite.yml +++ b/tests/codeception/console/unit.suite.yml @@ -3,6 +3,7 @@ modules: enabled: - Yii2: part: [orm, email, fixtures] + - tests\codeception\common\_support\Mockery config: Yii2: configFile: '../config/console/unit.php' diff --git a/tests/codeception/console/unit/TestCase.php b/tests/codeception/console/unit/TestCase.php index 7bd53f4..650812b 100644 --- a/tests/codeception/console/unit/TestCase.php +++ b/tests/codeception/console/unit/TestCase.php @@ -2,6 +2,7 @@ namespace tests\codeception\console\unit; use Codeception\Test\Unit; +use Mockery; class TestCase extends Unit { @@ -21,4 +22,9 @@ class TestCase extends Unit { return []; } + protected function tearDown() { + parent::tearDown(); + Mockery::close(); + } + }