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'
Related
With PHPUnit 6.5.14, I am trying to test a method. To do this, one of its dependencies needs to be mocked; however I can't get it working. Here is a stripped down version:
class Order {
public function create() {
$CCP = new CreditCardProcessor();
$success = $CCP->chargeCreditCard();
return $success;
}
}
class CreditCardProcessor {
public function chargeCreditCard() {
return false;
}
}
class OrderTest extends TestCase {
public function testCreate() {
$mockCCP = $this->getMockBuilder(CreditCardProcessor::class)
->setMethods(['chargeCreditCard'])
->getMock();
$mockCCP
->method('chargeCreditCard')
->willReturn(true);
$O = new Order();
$success = $O->create();
$this->assertTrue($success, 'Was not able to create order.');
}
}
I've read the docs and gone over some examples but can't figure it out. Any ideas what I am doing wrong? Thanks.
After finding more examples, I believe the solution is to pass in the dependency to create as an argument:
class Order {
public function create($CCP) {
$success = $CCP->chargeCreditCard();
return $success;
}
}
Then I can do the same in my test:
class OrderTest extends TestCase {
public function testCreate() {
$mockCCP = $this->getMockBuilder(CreditCardProcessor::class)
->setMethods(['chargeCreditCard'])
->getMock();
$mockCCP
->method('chargeCreditCard')
->willReturn(true);
$O = new Order();
$success = $O->create($mockCCP);
$this->assertTrue($success, 'Was not able to create order.');
}
}
I haven't tried yet, but that should do it.
I don't like the idea of changing code to satisfy tests, but it probably is also an indication I need to restructure my code.
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?
So I have 2 classes: Lets call them Person and Car
I need to access some attributes from my person object in my Car class before I can instantiate.
Do I simply say something to the order of: $car = new Car($person);
If yes, then how do I access those object attributes in my Car class? Would it be something like this:
class Car{
function __construct($person)
{
$this->person = $person;
}
}
If no, what would be a way to achieve this?
We have some confusion here. Real world OOP, consider and compare:
A car is given to or bought by a person:
$person->addCar($car);
A person is entering the car (to sit on the left front seat):
$car->addPerson($person, Car::SEAT_FRONT_LEFT);
Where Car::SEAT_FRONT_LEFT is a public const member of Car.
The relations are important to be kept semantically correct to be able to construct working objects.
--
To implement this, it might be helpful to lookup the meaning of (Aware-)Interface.
Example classes I'd probably define:
interface Person {
public function getHeight();
public function setHeight();
}
class CarOwner implements Person {
protected $height;
protected $cars = array();
public function getHeight();
public function setHeight();
public function getCars();
};
class Driver implements Person {
protected $height;
public function getHeight();
public function setHeight();
};
class Car {
protected $persons = array(); // #var array
protected $readyToDrive = false;
public const SEAT_FRONT_LEFT = 1;
public const SEAT_FRONT_RIGHT = 2;
// ...
public function addPerson(Person $person, $seat) {
if ($person instanceof Driver) {
$this->isReadyToDrive(true);
}
// I know this is stupid but you get the point:
if ($this->getDriver()->getHeight() < 140 && $this->getSeat()->getPosition() > 40) {
throw new Exception('please set the seat to fit your height!');
}
if (!in_array($person, $this->persons)) {
$this->persons[$seat] = $person;
}
else {
throw new Exception('person is already in the car!');
// Person first needs to "leave" the car! like:
// $car->removePerson($person)->addPerson($person, Car::SEAT_REAR_LEFT);
}
}
public function getDriver() {
if (isset($persons[self::SEAT_FRONT_LEFT]) && $persons[self::SEAT_FRONT_LEFT] instanceof Driver) {
return persons[self::SEAT_FRONT_LEFT];
}
else { //... }
}
};
If we want to see it from encapsulation side, we need to think about it. Is it ok, if a car knows a Person, which is inside? Maybe we need a Service/Controller which takes this params?
function processSomething(Person $person, Car $car) {}
But your example is not bad at all. It is depended on the usecase. Its the way it is done, if a car may know about his person.
If you can have multiple persons, you can have this constructor:
public function __construct(array $personList) {
//Check for a person
if (count($personList) < 1) {
throw new Exception('No person given');
}
//Validate list
foreach ($personList as $person) {
if (!$person instanceof Person) {
throw new Exception('This is not a person');
}
}
//Keep persons
$this->personList = $personList;
}
Also note, if we want to have multiple Objects in our class, we can make an ContainerObject:
class ContainerObject {
public $person;
public $moreInfo;
public $manyMoreInfo;
}
class Person {
public function sayHello() {};
}
class Car {
private $containerObj;
function __construct(ContainerObject $obj) {
$this->containerObj= $obj;
}
function foo() {
$this->containerObj->person->sayHello();
}
}
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));
}
}
I`m following this tutorial: http://jtreminio.com/2013/03/unit-testing-tutorial-part-5-mock-methods-and-overriding-constructors/ . A great tutorial for learn how works PHPUnit.
But I´m not able to understand because the test not pass.
The failure is:
Method was expected to be called 1 times, actually called 0 times.
At this part of code:
$badCode->expects($this->once())
->method('checkPassword')
->with($password);
But this is not possible because the next soft assertion runs inside checkPassword method and pass the test.
$badCode->expects($this->once())
->method('callExit');
It fails because is a mock method and the behaviour is different? Or the code is wrong?
I attach all the files for easy understanding, it is a small example.
Console
PHPUnit 3.7.18 by Sebastian Bergmann.
FYOU SHALL NOT PASS............
Time: 0 seconds, Memory: 6.00Mb
There was 1 failure:
1) phpUnitTutorial\Test\BadCodeTest::testAuthorizeExitsWhenPasswordNotSet
Expectation failed for method name is equal to <string:checkPassword> when invoked 1 time(s).
Method was expected to be called 1 times, actually called 0 times.
FAILURES!
Tests: 13, Assertions: 14, Failures: 1.
BadCode.php
<?php
namespace phpUnitTutorial;
class BadCode
{
protected $user;
public function __construct(array $user)
{
$this->user = $user;
}
public function authorize($password)
{
if ($this->checkPassword($password)) {
return true;
}
return false;
}
protected function checkPassword($password)
{
if (empty($user['password']) || $user['password'] !== $password) {
echo 'YOU SHALL NOT PASS';
$this->callExit();
}
return true;
}
protected function callExit()
{
exit;
}
}
BadCodeTest.php
<?php
namespace phpUnitTutorial\Test;
class BadCodeTest extends \PHPUnit_Framework_TestCase
{
public function testAuthorizeExitsWhenPasswordNotSet()
{
$user = array('username' => 'jtreminio');
$password = 'foo';
$badCode = $this->getMockBuilder('phpUnitTutorial\BadCode')
->setConstructorArgs(array($user))
->setMethods(array('callExit'))
->getMock();
$badCode->expects($this->once())
->method('checkPassword')
->with($password);
$badCode->expects($this->once())
->method('callExit');
$this->expectOutputString('YOU SHALL NOT PASS');
$badCode->authorize($password);
}
}
Someone can help me? Thanks!
Update
The author of the blog updated the tutorial with the solution.
Can't do any assertions against mock methods, only stubs.
BadCode.php
<?php
namespace phpUnitTutorial;
class BadCode
{
protected $user;
public function __construct(array $user)
{
$this->user = $user;
}
public function authorize($password)
{
if ($this->checkPassword($password)) {
return true;
}
return false;
}
protected function checkPassword($password)
{
if (empty($this->user['password']) || $this->user['password'] !== $password) {
echo 'YOU SHALL NOT PASS';
$this->callExit();
}
return true;
}
protected function callExit()
{
exit;
}
}
BadCodeTest.php
<?php
namespace phpUnitTutorial\Test;
class BadCodeTest extends \PHPUnit_Framework_TestCase
{
public function testAuthorizeExitsWhenPasswordNotSet()
{
$user = array('username' => 'jtreminio');
$password = 'foo';
$badCode = $this->getMockBuilder('phpUnitTutorial\BadCode')
->setConstructorArgs(array($user))
->setMethods(array('callExit'))
$badCode->expects($this->once())
->method('callExit');
$this->expectOutputString('YOU SHALL NOT PASS');
$badCode->authorize($password);
}
}
author here - I fudged that part up, and AppFog (my host) is having issues with free accounts (mine) so I can't update it right now.
Also, yes it should be $this->user in checkPassword()
You're missing a method in setMethods :
->setMethods(array('callExit'))
You need to add checkPassword too if you want to check if it is called.