I am trying to write some unit test where I can check if creator of a post should not receive same email twice.
I am new at testing (not new in Symfony.) I tried some basic test and they work.
First of the problem is that this is a method inside of and DataPersister class within Api Platform which is called everytime entity is persisted and it sends an email to a user..
final class EmailDataPersister
{
public function persist($data, array $context = [])
{
$result = $this->decorated->persist($data, $context);
if (
$data instanceof User&& (
($context['collection_operation_name'] ?? null) === 'post')
) {
$this->sendEmail($data);
}
return $result;
}
private function sendEmail(User $user)
{
$email = (new EmailNotification())
->setTitle('Hello ' . $user->getName())
->setFrom('sender#sent.com')
->setAddress($user->getEmail)
->setBody('This is a new email');
$this->messageBus->dispatch($email);
}
}
Tried something like:
class someModelTest extends TestCase
public function testEmails()
{
$this->satEmail('test#pmg.co');
$this->assertMessageCount(1, 'should have sent one email');
$msg = $this->getMessages()[0];
$this->assertArrayHasKey('test#pmg.co', $msg->getTo());
}
public function satEmail($email)
{
$msg = (new \Swift_Message('Testing..'))
->setSubject('Hello')
->setBody('Hello')
->setFrom('helle#example.com')
->setTo($email);
$this->mailer->send($msg);
}
I think I need to add..
$model = $this->getMock('someModel', array('setFrom', 'setTo', 'setSubject', 'send'));
$controller->expects($this->once())
->method('setFrom');
$controller->expects($this->once())
->method('setTo');
$controller->expects($this->once())
->method('setSubject');
$controller->expects($this->once())
->method('send');
$model->sendEmail();
}
I can not figure out should I call that method inside the class or what should I define model controller etc?
Related
I'm trying to write my custom laravel channel notifications, like this: https://laravel.com/docs/5.7/notifications#custom-channels.
I wrote this code:
AimonChannel.php
class AimonChannel{
public function send($notifiable, Notification $notification)
{
$message = $notification->toAimon($notifiable);
if (is_string($message)) {
$message = new AimonMessage($message);
}
//some code to send sms with curl
}
AimonMessage.php
class AimonMessage
{
public $messaggio = "";
public function messaggio($messaggio){
$this->messaggio = $messaggio;
}
}
SendAimonMessage.php
class SendAimonMessage extends Notification
{
use Queueable;
protected $messaggio;
public function __construct($messaggio)
{
$this->messaggio = $messaggio;
}
public function via($notifiable)
{
return [AimonChannel::class];
}
public function toAimon($notifiable)
{
return (new AimonMessage())
->messaggio($this->messaggio);
}
}
So, the code:
$user->notify(new SendAimonMessage('my custom message'));
is sent, but without the text.
The problem is in the send() function of AimonChannel; the $message variable is always null.
Where is my code mistake?
Thanks!
add return statement in the messaggio function like this:
class AimonMessage {
public $messaggio = "";
public function messaggio($messaggio){
$this->messaggio = $messagio;
return $this;
}
}
Sounds simple enough but I cannot work out how to pass data to the below mailable for a test. It works fine for normal use.
Working, through controller
Mail::to($user)->send(new C2cMail($this->commitment->data, \App\EModule::findOrFail($this->commitment->module_id), $user));
Mailable
public function __construct($commitment, Module $module, User $user)
{
$this->commitment = unserialize($commitment);
$this->module = $module;
$this->user = $user;
$this->title = $this->module->title;
}
/**
* Build the message.
*
* #return $this
*/
public function build()
{
try {
if(empty(env('MAIL_FROM')) || !env('MAIL_FROM') || env('MAIL_FROM') == null){
throw new \Exception("You must set your MAIL_FROM environment variable", 406);
}
$this->from(env('MAIL_FROM'))->subject('First email test')->view('mail.c2c-1');
} catch (\Exception $e) {
\Log::error($e->getMessage());
}
}
So this all works fine but I cannot get a unit test to pass data to the mailable constructor.
Test case
public function testC2cEmailSend()
{
Mail::fake();
// Grab any old C2C entry
$c2c = \App\Commitment::with(['module', 'user'])->first();
$data = $c2c->data;
$user = $c2c->user;
$module = $c2c->module;
$mailable_data = ['data' => $data, 'user' => $user, 'module' => $module];
Mail::assertSent(C2cFirst::class, function($mail) use ($mailable_data) {
return $mail->hasTo($mailable_data['user']->email);
});
}
Self resolved
It was a misunderstanding on how Mail::fake() actually works. It hijacks the Mail class and replaces it with an instance of MailFake; and so placing it above the desired functionality prevents actual sending.
I am trying to learn how to create unit tests for my custom "framework" and here is a method that verifies the email address when user is registering.
private function verifyEmail()
{
if(empty($this->email) || empty($this->email_repeat)) {
throw new \Exception('please enter email');
}
if($this->email != $this->email_repeat) {
throw new \Exception('emails don\'t match');
}
if(!filter_var($this->email, FILTER_VALIDATE_EMAIL)) {
throw new \Exception('E-mail is invalid');
}
$isTaken = $this->db->getRow("SELECT COUNT(*) as count FROM users WHERE email = ?", [$this->email]);
if($isTaken->count > 0){
throw new \Exception('E-mail is taken');
}
}
And here is the unit test
class RegisterTest extends \PHPUnit\Framework\TestCase
{
public function testVerifyEmail() {
// what do i type here?
}
}
So, what do I type in the testVerifyEmail() method to pass an email to be tested? I am going through the documentation but as a newbie the information is overwhelming and I can't find a solution.
You can use PhpUnit DataProvider to provide parameters for your test methods.
https://phpunit.de/manual/6.5/en/writing-tests-for-phpunit.html#writing-tests-for-phpunit.data-providers
The example here will execute testMethod 4 times (one for each $data item).
<?php
use PHPUnit\Framework\TestCase;
class DataTest extends TestCase
{
/**
* #dataProvider myProvider
*/
public function testMethod($a, $b, $expected)
{
var_dump($a,$b,$expected);
//... your assertions here
}
public function myProvider()
{
$data = [
//each item represents the related method parameter
//the first time $a = 'valueOfA-0', $b='valueOfB-0',$expected='valueOfExpected-0'
//and so on, for each array
['valueOfA-0', 'valueOfB-0', 'valueOfExpected-0'],
['valueOfA-1', 'valueOfB-1', 'valueOfExpected-1'],
['valueOfA-2', 'valueOfB-2', 'valueOfExpected-2'],
['valueOfA-3', 'valueOfB-3', 'valueOfExpected-3'],
];
return $data;
}
}
//values of testMethod parameters each time
//$a = 'valueOfA-0', $b='valueOfB-0', $expected='valueOfExpected-0'
//$a = 'valueOfA-1', $b='valueOfB-1', $expected='valueOfExpected-1'
//$a = 'valueOfA-2', $b='valueOfB-2', $expected='valueOfExpected-2'
//$a = 'valueOfA-3', $b='valueOfB-3', $expected='valueOfExpected-3'
Please consider the following PHP code
static public function IsLicenseValid($domain)
{
if (empty($domain)) {
throw new Exception;
}
$licenseResponse = Curl::Post(['hostname' => $domain]);
$Xml = new XML();
$xmlTree = $Xml->XMLToTree($licenseResponse);
if ($xmlTree['is_valid'] == 'true') {
return true;
}
}
return false;
}
I am writing test case using PHPUnit to check the above method. I am able to cover all cases except one case in which a domain license should return true in is_valid xml node.
The REST API is so secured that it does not accept request from IPs that are not listed in their whitelist. And if someone is making a request from a non-whitelisted IP the API returns a false value for is_valid (and this is how I am covering the case for false)
I know this can be done using a mock object but I am not really sure how to write a mock object that can cover the case where a domain name is valid. Can someone please help?
Thanks in advance
To test this class, you would mock the Curl::Post call, but since it is done inline, you need to use Dependency Injection to use the Mock.
Class:
class ValidateLicense {
private $svc;
// Constructor Injection, pass the IntegratedService object here
public function __construct($Service = NULL)
{
if(! is_null($Service) )
{
if($Service instanceof LicenseAPI)
{
$this->SetService($Service);
}
}
}
function SetService(LicenseAPI $Service)
{
$this->svc = $Service
}
function ValidLicense($domain) {
$svc = $this->svc;
$result = $svc->IsLicenseValid($domain);
return $result;
}
}
class LicenseAPI {
public function IsLicenseValid($domain)
{
if( empty($domain)) {
throw new Exception;
}
$licenseResponse = Curl::Post(['hostname' => $domain]);
$Xml = new XML();
$xmlTree = $Xml->XMLToTree($licenseResponse);
if ($xmlTree['is_valid'] == 'true') {
return true;
}
return false;
}
}
Test:
class ValidateLicenseTest extends PHPUnit_Framework_TestCase
{
// Could also use dataProvider to send different returnValues, and then check with Asserts.
public function testValidLicense()
{
// Create a mock for the LicenseAPI class,
$MockService = $this->getMock('LicenseAPI', array('IsLicenseValid'));
// Set up the expectation for the return method
$MockService->expects($this->any())
->method('IsLicenseValid')
->will($this->returnValue(true));
// Create Test Object - Pass our Mock as the service
$TestClass = new ValidateLicense($MockService);
// Or
// $TestClass = new ValidateLicense();
// $TestClass->SetServices($MockService);
// Test
$domain = "localhost"; // Could be checked with the Mock functions
$this->assertTrue($TestClass->ValidLicense($domain));
}
// Could also use dataProvider to send different returnValues, and then check with Asserts.
public function testInValidLicense()
{
// Create a mock for the LicenseAPI class,
$MockService = $this->getMock('LicenseAPI', array('IsLicenseValid'));
// Set up the expectation for the return method
$MockService->expects($this->any())
->method('IsLicenseValid')
->will($this->returnValue(false));
// Create Test Object - Pass our Mock as the service
$TestClass = new ValidateLicense($MockService);
// Or
// $TestClass = new ValidateLicense();
// $TestClass->SetServices($MockService);
// Test
$domain = "localhost"; // Could be checked with the Mock functions
$this->assertFalse($TestClass->ValidLicense($domain));
}
}
How I'm stuck with writing a test for the following code. I want to mock the $userModel but how can I add this to the test?
class PC_Validate_UserEmailDoesNotExist extends Zend_Validate_Abstract
{
public function isValid($email, $context = NULL)
{
$userModel = new Application_Model_User();
$user = $userModel->findByEmailReseller($email, $context['reseller']);
if ($user == NULL) {
return TRUE;
} else {
return FALSE;
}
}
}
Update: the Solution
I did change my class to the following to get it testable, it now uses dependency injection. More information about dependency injection you van find out here
I now call the class like this:
new PC_Validate_UserEmailDoesNotExist(new Application_Model_User()
The refactored class
class PC_Validate_UserEmailDoesNotExist extends Zend_Validate_Abstract
{
protected $_userModel;
public function __construct($model)
{
$this->_userModel = $model;
}
public function isValid($email, $context = NULL)
{
if ($this->_userModel->findByEmailReseller($email, $context['reseller']) == NULL) {
return TRUE;
} else {
return FALSE;
}
}
}
The unit test
class PC_Validate_UserEmailDoesNotExistTest extends BaseControllerTestCase
{
protected $_userModelMock;
public function setUp()
{
parent::setUp();
$this->_userModelMock = $this->getMock('Application_Model_User', array('findByEmailReseller'));
}
public function testIsValid()
{
$this->_userModelMock->expects($this->once())
->method('findByEmailReseller')
->will($this->returnValue(NULL));
$validate = new PC_Validate_UserEmailDoesNotExist($this->_userModelMock);
$this->assertTrue(
$validate->isValid('jef#test.com', NULL),
'The email shouldn\'t exist'
);
}
public function testIsNotValid()
{
$userStub = new \Entities\User();
$this->_userModelMock->expects($this->once())
->method('findByEmailReseller')
->will($this->returnValue($userStub));
$validate = new PC_Validate_UserEmailDoesNotExist($this->_userModelMock);
$this->assertFalse(
$validate->isValid('jef#test.com', NULL),
'The email should exist'
);
}
}
Short answer: you cant, because you hardcoded the dependency into the method.
There is three workarounds for this:
1) Make the used classname configurable, so you can do something like:
$className = $this->userModelClassName;
$userModel = new $className();
or 2) Add a third param to the method signature that allows passing in the dependency
public function isValid($email, $context = NULL, $userModel = NULL)
{
if($userModel === NULL)
{
$userModel = new Application_Model_User();
}
// ...
}
or 3) use set_new_overload() as described in
http://sebastian-bergmann.de/archives/885-Stubbing-Hard-Coded-Dependencies.html
http://github.com/sebastianbergmann/php-test-helpers
Note: the Test-Helper extension is superseded by https://github.com/krakjoe/uopz