UnitTest object mocking or real object - php

I had a discussion with my Team Lead, regarding UnitTest, the question was,
In UnitTest do we use Object Mocking or use the Real Object?
I was supporting the Object Mocking concept, as we should only input/output data from Objects.
At the end we agreed to use Real object instead of Mocking so the following was my Test
<?php
namespace App\Services\Checkout\Module\PaymentMethodRules;
use App\Library\Payment\Method;
use App\Services\Checkout\Module\PaymentMethodRuleManager;
class AdminRule implements PaymentMethodRule
{
/**
* #var boolean
*/
private $isAdmin;
/**
* #var bool
*/
private $isBankTransferAvailable;
/**
* #param boolean $isAdmin
* #param bool $isBankTransferAvailable
*/
public function __construct($isAdmin, $isBankTransferAvailable)
{
$this->isAdmin = $isAdmin;
$this->isBankTransferAvailable = $isBankTransferAvailable;
}
/**
* #param PaymentMethodRuleManager $paymentMethodRuleManager
*/
public function run(PaymentMethodRuleManager $paymentMethodRuleManager)
{
if ($this->isAdmin) {
$paymentMethodRuleManager->getList()->add([Method::INVOICE]);
}
if ($this->isAdmin && $this->isBankTransferAvailable) {
$paymentMethodRuleManager->getList()->add([Method::BANK_TRANSFER]);
}
}
}
<?php
namespace tests\Services\Checkout\Module;
use App\Library\Payment\Method;
use App\Services\Checkout\Module\PaymentMethodList;
use App\Services\Checkout\Module\PaymentMethodRuleManager;
use App\Services\Checkout\Module\PaymentMethodRules\AdminRule;
class AdminRuleTest extends \PHPUnit_Framework_TestCase
{
const IS_ADMIN = true;
const IS_NOT_ADMIN = false;
const IS_BANK_TRANSFER = true;
const IS_NOT_BANK_TRANSFER = false;
/**
* #test
* #dataProvider runDataProvider
*
* #param bool $isAdmin
* #param bool $isBankTransferAvailable
* #param array $expected
*/
public function runApplies($isAdmin, $isBankTransferAvailable, $expected)
{
$paymentMethodRuleManager = new PaymentMethodRuleManager(
new PaymentMethodList([]),
new PaymentMethodList([])
);
$adminRule = new AdminRule($isAdmin, $isBankTransferAvailable);
$adminRule->run($paymentMethodRuleManager);
$this->assertEquals($expected, $paymentMethodRuleManager->getList()->get());
}
/**
* #return array
*/
public function runDataProvider()
{
return [
[self::IS_ADMIN, self::IS_BANK_TRANSFER, [Method::INVOICE, Method::BANK_TRANSFER]],
[self::IS_ADMIN, self::IS_NOT_BANK_TRANSFER, [Method::INVOICE]],
[self::IS_NOT_ADMIN, self::IS_BANK_TRANSFER, []],
[self::IS_NOT_ADMIN, self::IS_NOT_BANK_TRANSFER, []]
];
}
}
My question is, in Unit Test should is use Real Objects or Object Mocking and why?
Second Question, the given Unit test is right or wrong in terms of Unit testing.

The generic answer to such a generic question is: you prefer to use as much of "real" code as possible when doing unit tests. Real code should be default, mocked code is the exception!
But of course, there are various valid reasons to use mocking:
The "real" code does not work in your test setup.
You want to use your mocking framework also to verify that certain actions took place
Example: the code that you intend to test makes a call to some remote service (maybe a database server). Of course that means that you need some tests that do the end to end testing. But for many tests, it might be much more convenient to not do that remote call; instead you would use mocking here - to avoid the remote database call.
Alternatively, as suggested by John Joseph; you might also start with mocking all/most dependencies; to then gradually replace mocking with real calls. This process can help with staying focused on testing exactly "that part" that you actually want to test (instead of getting lost in figuring why your tests using "real other code" is giving you troubles).

IMHO I think it would be good if the original code could be tested directly without any mocking as this would make it less error-prone, and would avoid the debate that if the mocked object behaves almost the same as the original one, but we are not living in the world of unicorns anymore, and mocking is a necessary evil or it is not? This remains the question.
So I think I can rephrase your question to be when to use dummy, fake, stub, or mock?
Generally, the aforementioned terms are known as Test doubles.
As a start, you can check this answer here
Some of the cases when test doubles might be good:
The object under test/System Under Test (SUT) a lot of dependencies, that are required for initialization purposes, and these dependencies would not affect the test, so these dependencies can be dummy ones.
/**
* #inheritdoc
*/
protected function setUp()
{
$this->servicesManager = new ServicesManager(
$this->getDummyEntity()
// ........
);
}
/**
* #return \PHPUnit_Framework_MockObject_MockObject
*/
private function getDummyEntity()
{
return $this->getMockBuilder(Entity\Entity1::class)
->disableOriginalConstructor()
->setMethods([])
->getMock();
}
SUT has an external dependencies such as an Infrastructure/Resource (e.g. web service, database, cash, file …), then it is a good approach to fake that by using in-memory representation, as one of the reasons to do that is to avoid cluttering this Infrastructure/Resource with test data.
/**
* #var ArrayCollection
*/
private $inMemoryRedisDataStore;
/**
* #var DataStoreInterface
*/
private $fakeDataStore;
/**
* #inheritdoc
*/
protected function setUp()
{
$this->inMemoryRedisDataStore = new Collections\ArrayCollection;
$this->fakeDataStore = $this->getFakeRedisDataStore();
$this->sessionHandler = new SessionHanlder($this->fakeDataStore);
}
/**
* #return \PHPUnit_Framework_MockObject_MockObject
*/
private function getFakeRedisDataStore()
{
$fakeRedis = $this->getMockBuilder(
Infrastructure\Memory\Redis::class
)
->disableOriginalConstructor()
->setMethods(['set', 'get'])
->getMock();
$inMemoryRedisDataStore = $this->inMemoryRedisDataStore;
$fakeRedis->method('set')
->will(
$this->returnCallback(
function($key, $data) use ($inMemoryRedisDataStore) {
$inMemoryRedisDataStore[$key] = $data;
}
)
);
$fakeRedis->method('get')
->will(
$this->returnCallback(
function($key) use ($inMemoryRedisDataStore) {
return $inMemoryRedisDataStore[$key];
}
)
);
}
When there is a need of asserting the state of SUT, then stubs become handy. Usually, this would be confused with a fake object, and to clear this out, fake objects are helping objects and they should never be asserted.
/**
* Interface Provider\SMSProviderInterface
*/
interface SMSProviderInterface
{
public function send();
public function isSent(): bool;
}
/**
* Class SMSProviderStub
*/
class SMSProviderStub implements Provider\SMSProviderInterface
{
/**
* #var bool
*/
private $isSent;
/**
* #inheritdoc
*/
public function send()
{
$this->isSent = true;
}
/**
* #return bool
*/
public function isSent(): bool
{
return $this->isSent;
}
}
/**
* Class PaymentServiceTest
*/
class PaymentServiceTest extends \PHPUnit_Framework_TestCase
{
/**
* #var Service\PaymentService
*/
private $paymentService;
/**
* #var SMSProviderInterface
*/
private $smsProviderStub;
/**
* #inheritdoc
*/
protected function setUp()
{
$this->smsProviderStub = $this->getSMSProviderStub();
$this->paymentService = new Service\PaymentService(
$this->smsProviderStub
);
}
/**
* Checks if the SMS was sent after payment using stub
* (by checking status).
*
* #param float $amount
* #param bool $expected
*
* #dataProvider sMSAfterPaymentDataProvider
*/
public function testShouldSendSMSAfterPayment(float $amount, bool $expected)
{
$this->paymentService->pay($amount);
$this->assertEquals($expected, $this->smsProviderStub->isSent());
}
/**
* #return array
*/
public function sMSAfterPaymentDataProvider(): array
{
return [
'Should return true' => [
'amount' => 28.99,
'expected' => true,
],
];
}
/**
* #return Provider\SMSProviderInterface
*/
private function getSMSProviderStub(): Provider\SMSProviderInterface
{
return new SMSProviderStub();
}
}
If the behavior of SUT should be checked then mocks most probably will come to the rescue or stubs (Test spy), it can be detected as simple as that most probably no assert statements should be found. for example, the mock can be setup to behave like when it get a call to X method with values a, and b return the value Y or expect a method to be called once or N of times, ..etc.
/**
* Interface Provider\SMSProviderInterface
*/
interface SMSProviderInterface
{
public function send();
}
class PaymentServiceTest extends \PHPUnit_Framework_TestCase
{
/**
* #var Service\PaymentService
*/
private $paymentService;
/**
* #inheritdoc
*/
protected function setUp()
{
$this->paymentService = new Service\PaymentService(
$this->getSMSProviderMock()
);
}
/**
* Checks if the SMS was sent after payment using mock
* (by checking behavior).
*
* #param float $amount
*
* #dataProvider sMSAfterPaymentDataProvider
*/
public function testShouldSendSMSAfterPayment(float $amount)
{
$this->paymentService->pay($amount);
}
/**
* #return array
*/
public function sMSAfterPaymentDataProvider(): array
{
return [
'Should check behavior' => [
'amount' => 28.99,
],
];
}
/**
* #return SMSProviderInterface
*/
private function getSMSProviderMock(): SMSProviderInterface
{
$smsProviderMock = $this->getMockBuilder(Provider\SMSProvider::class)
->disableOriginalConstructor()
->setMethods(['send'])
->getMock();
$smsProviderMock->expects($this->once())
->method('send')
->with($this->anything());
}
}
Corner cases
SUT has a lot of dependencies which are dependent on other things, and to avoid this dependency loop as we are only interested in testing some methods, the whole object can be mocked, but with having the ability to forward the calls to the original methods.
$testDouble = $this->getMockBuilder(Entity\Entity1::class)
->disableOriginalConstructor()
->setMethods(null);

As per Ahmed Kamal's answer, it worked as expected.
I tested the below sample.
Foo.php
<?php
class Foo
{
/**
* Tell Foo class Name
* #param string $name
* #return string
*/
public function tellName(string $name = 'Josh'): string
{
return 'Hi ' . $name;
}
}
FooTest.php
<?php
include('Foo.php');
use PHPUnit\Framework\TestCase;
class FooTest extends TestCase
{
/**
* PHPUnit testing with assertEquals
* #return void
*/
public function testTellName()
{
// create the class object
$mockObj = $this->getMockBuilder(Foo::class)
->disableOriginalConstructor()
->setMethods(null)
->getMock();
// get the object function result by passing the method parameter value
// pass different parameter value to get an invalid result
$result = $mockObj->tellName('John');
// validate the result with assertEquals()
$this->assertEquals('Hi John', $result);
}
}
Error and Success results:
Cheers!

Related

static and non static methods in mvc websites

currently i have a problem which don't allow me to continue adding features to my mvc website without do any sort of spaghetti code.
i have two classes, one is ModModel and the other is ModUploadModel. both are extended with the Model class.
ModModel contains all the methods about "mods", as ModModel->doesModNameExists(), ModModel->getModDetails() etc...
ModUploadModel contains all the methods for the uploading of a mod, as ModUploadModel->upload(), ModUploadModel->isModNameValid() etc...
in some cases i have to call some ModModel methods from ModUploadModel, and to do so i have to create a new instance of ModModel inside the ModUploadController and to pass it as an argument to ModUploadModel->upload().
for example: the ModUploadController creates two new objects, $modModel = new ModModel() and $modUploadModel = new ModUploadModel(), then calls $modUploadModel->upload($modModel).
this is the ModUploadController, which creates the two objects and call the ModUploadModel->upload() method
class ModUploadController extends Mvc\Controller {
public function uploadMod(): void {
$modUploadModel = new ModUploadModel()
$modModel = new ModModel();
// $modModel needs to be passed because the ModUploadModel needs
// one of its methods
if ($modUploadModel->upload("beatiful-mod", $modModel)) {
// success
} else {
// failure
}
}
}
ModUploadModel->upload() checks if the input is valid (if the mod name isn't already taken etc), and finally upload the mod data into the db. obviously it's all suddivise in more sub private methods, as ModUploadModel->isModNameValid() and ModUploadModel->insertIntoDb().
the problem is that i don't structured my classes with all static methods, and everytime i have to pass objects as parameters, like with ModModel (for example i need its isModNameValid() method).
i thought about making all the ModModel methods static, but that's not as simple as it seems, because all its methods query the db, and they use the Model->executeStmt() method (remember that all the FooBarModel classes are extended with the Model class, which contains usefull common methods as executeStmt() and others), and calling a non static method from a static one is not a good practice in php, so i should make static the Model methods too, and consequently also the Dbh methods for the db connection (Model is extended with Dbh).
the ModModel class:
class ModModel extends Mvc\Model {
// in reality it queries the db with $this->executeStmt(),
// which is a Model method
public function doesModNameExists($name) {
if (/* exists */) {
return true;
}
return false;
}
}
the ModUploadModel class:
class ModUploadModel extends Mvc\Model {
private $modName;
public function upload($modName, $modModel) {
$this->modName = $modName;
if (!$this->isModNameValid($modModel)) {
return false;
}
if ($this->insertIntoDb()) {
return true;
}
return false;
}
// this methods needs to use the non static doesModNameExists() method
// which is owned by the ModModel class, so i need to pass
// the object as an argument
private function isModNameValid($modModel) {
if ($modModel->doesModNameExists($this->modName)) {
return false;
}
// other if statements
return true;
}
private function insertIntoDb() {
$sql = "INSERT INTO blabla (x, y) VALUES (?, ?)";
$params = [$this->modName, "xxx"];
if ($this->executeStmt($sql, $params)) {
return true;
}
return false;
}
}
the alternative would be to create a new instance of Model inside the ModModel methods, for example (new Model)->executeStmt(). the problem is that it's not a model job to create new objects and generally it's not the solution i like most.
Some observations and suggestions:
[a] You are passing a ModModel object to ModUploadModel to validate the mod name before uploading. You shouldn't even try to call ModUploadModel::upload() if a mod with the provided name already exists. So you should follow steps similar to this:
class ModUploadController extends Mvc\Controller {
public function uploadMod(): void {
$modUploadModel = new ModUploadModel()
$modModel = new ModModel();
$modName = 'beatiful-mod';
try {
if ($modModel->doesModNameExists($modName)) {
throw new \ModNameExistsException('A mod with the name "' . $modName . '" already exists');
}
$modUploadModel->upload($modName);
} catch (\ModNameExistsException $exception){
// ...Present the exception message to the user. Use $exception->getMessage() to get it...
}
}
}
[b] Creating objects inside a class is a bad idea (like in ModUploadController). Use dependency injection instead. Read this and watch this and this. So the solution would look something like this:
class ModUploadController extends Mvc\Controller {
public function uploadMod(ModUploadModel $modUploadModel, ModModel $modModel): void {
//... Use the injected objects ($modUploadModel and $modModel ) ...
}
}
In a project, all objects that need to be injected into others can be created by a "dependency injection container". For example, PHP-DI (which I recommend), or other DI containers. So, a DI container takes care of all dependency injections of your project. For example, in your case, the two objects injected into ModUploadController::uploadMod method would be automatically created by PHP-DI. You'd just have to write three lines of codes in the file used as the entry-point of your app, probably index.php:
use DI\ContainerBuilder;
$containerBuilder = new ContainerBuilder();
$containerBuilder->useAutowiring(true);
$container = $containerBuilder->build();
Of course, a DI container requires configuration steps as well. But, in a couple of hours, you can understand how and where to do it.
By using a DI container, you'll be able to concentrate yourself solely on the logic of your project, not on how and where various components should be created, or similar tasks.
[c] Using static methods is a bad idea. My advise would be to get rid of all static methods that you already wrote. Watch this, read this, this and this. So the solution to the injection problem(s) that you have is the one above: the DI, perfomed by a DI container. Not at all creating static methods.
[d] You are using both components to query the database (ModModel with doesModNameExists() and ModUploadModel with insertIntoDb()). You should dedicate only one component to deal with the database.
[e] You don't need Mvc\Model at all.
[f] You don't need Mvc\Controller at all.
Some code:
I wrote some code, as an alternative to yours (from which I somehow "deduced" the tasks). Maybe it will help you, seeing how someone else would code. It would give you the possibility of "adding features to my mvc website without do any sort of spaghetti code". The code is very similar to the one from an answer that I wrote a short time ago. That answer also contains additional important suggestions and resources.
Important: Note that the application services, e.g. all components from Mvc/App/Service/, should communicate ONLY with the domain model components, e.g. with the components from Mvc/Domain/Model/ (mostly interfaces), not from Mvc/Domain/Infrastructure/. In turn, the DI container of your choice will take care of injecting the proper class implementations from Mvc/Domain/Infrastructure/ for the interfaces of Mvc/Domain/Model/ used by the application services.
Note: my code uses PHP 8.0. Good luck.
Project structure:
Mvc/App/Controller/Mod/AddMod.php:
<?php
namespace Mvc\App\Controller\Mod;
use Psr\Http\Message\{
ResponseInterface,
ServerRequestInterface,
};
use Mvc\App\Service\Mod\{
AddMod As AddModService,
Exception\ModAlreadyExists,
};
use Mvc\App\View\Mod\AddMod as AddModView;
class AddMod {
/**
* #param AddModView $addModView A view for presenting the response to the request back to the user.
* #param AddModService $addModService An application service for adding a mod to the model layer.
*/
public function __construct(
private AddModView $addModView,
private AddModService $addModService,
) {
}
/**
* Add a mod.
*
* The mod details are submitted from a form, using the HTTP method "POST".
*
* #param ServerRequestInterface $request A server request.
* #return ResponseInterface The response to the current request.
*/
public function addMod(ServerRequestInterface $request): ResponseInterface {
// Read the values submitted by the user.
$name = $request->getParsedBody()['name'];
$description = $request->getParsedBody()['description'];
// Add the mod.
try {
$mod = $this->addModService->addMod($name, $description);
$this->addModView->setMod($mod);
} catch (ModAlreadyExists $exception) {
$this->addModView->setErrorMessage(
$exception->getMessage()
);
}
// Present the results to the user.
$response = $this->addModView->addMod();
return $response;
}
}
Mvc/App/Service/Mod/Exception/ModAlreadyExists.php:
<?php
namespace Mvc\App\Service\Mod\Exception;
/**
* An exception thrown if a mod already exists.
*/
class ModAlreadyExists extends \OverflowException {
}
Mvc/App/Service/Mod/AddMod.php:
<?php
namespace Mvc\App\Service\Mod;
use Mvc\Domain\Model\Mod\{
Mod,
ModMapper,
};
use Mvc\App\Service\Mod\Exception\ModAlreadyExists;
/**
* An application service for adding a mod.
*/
class AddMod {
/**
* #param ModMapper $modMapper A data mapper for transfering mods
* to and from a persistence system.
*/
public function __construct(
private ModMapper $modMapper
) {
}
/**
* Add a mod.
*
* #param string|null $name A mod name.
* #param string|null $description A mod description.
* #return Mod The added mod.
*/
public function addMod(?string $name, ?string $description): Mod {
$mod = $this->createMod($name, $description);
return $this->storeMod($mod);
}
/**
* Create a mod.
*
* #param string|null $name A mod name.
* #param string|null $description A mod description.
* #return Mod The newly created mod.
*/
private function createMod(?string $name, ?string $description): Mod {
return new Mod($name, $description);
}
/**
* Store a mod.
*
* #param Mod $mod A mod.
* #return Mod The stored mod.
* #throws ModAlreadyExists The mod already exists.
*/
private function storeMod(Mod $mod): Mod {
if ($this->modMapper->modExists($mod)) {
throw new ModAlreadyExists(
'A mod with the name "' . $mod->getName() . '" already exists'
);
}
return $this->modMapper->saveMod($mod);
}
}
Mvc/App/View/Mod/AddMod.php:
<?php
namespace Mvc\App\View\Mod;
use Mvc\{
App\View\View,
Domain\Model\Mod\Mod,
};
use Psr\Http\Message\ResponseInterface;
/**
* A view for adding a mod.
*/
class AddMod extends View {
/** #var Mod A mod. */
private Mod $mod = null;
/**
* Add a mod.
*
* #return ResponseInterface The response to the current request.
*/
public function addMod(): ResponseInterface {
$bodyContent = $this->templateRenderer->render('#Templates/Mod/AddMod.html.twig', [
'activeNavItem' => 'AddMod',
'mod' => $this->mod,
'error' => $this->errorMessage,
]);
$response = $this->responseFactory->createResponse();
$response->getBody()->write($bodyContent);
return $response;
}
/**
* Set the mod.
*
* #param Mod $mod A mod.
* #return static
*/
public function setMod(Mod $mod): static {
$this->mod = $mod;
return $this;
}
}
Mvc/App/View/View.php:
<?php
namespace Mvc\App\View;
use Psr\Http\Message\ResponseFactoryInterface;
use SampleLib\Template\Renderer\TemplateRendererInterface;
/**
* A view.
*/
abstract class View {
/** #var string An error message */
protected string $errorMessage = '';
/**
* #param ResponseFactoryInterface $responseFactory A response factory.
* #param TemplateRendererInterface $templateRenderer A template renderer.
*/
public function __construct(
protected ResponseFactoryInterface $responseFactory,
protected TemplateRendererInterface $templateRenderer
) {
}
/**
* Set the error message.
*
* #param string $errorMessage An error message.
* #return static
*/
public function setErrorMessage(string $errorMessage): static {
$this->errorMessage = $errorMessage;
return $this;
}
}
Mvc/Domain/Infrastructure/Mod/PdoModMapper.php:
<?php
namespace Mvc\Domain\Infrastructure\Mod;
use Mvc\Domain\Model\Mod\{
Mod,
ModMapper,
};
use PDO;
/**
* A data mapper for transfering Mod entities to and from a database.
*
* This class uses a PDO instance as database connection.
*/
class PdoModMapper implements ModMapper {
/**
* #param PDO $connection Database connection.
*/
public function __construct(
private PDO $connection
) {
}
/**
* #inheritDoc
*/
public function modExists(Mod $mod): bool {
$sql = 'SELECT COUNT(*) as cnt FROM mods WHERE name = :name';
$statement = $this->connection->prepare($sql);
$statement->execute([
':name' => $mod->getName(),
]);
$data = $statement->fetch(PDO::FETCH_ASSOC);
return ($data['cnt'] > 0) ? true : false;
}
/**
* #inheritDoc
*/
public function saveMod(Mod $mod): Mod {
if (isset($mod->getId())) {
return $this->updateMod($mod);
}
return $this->insertMod($mod);
}
/**
* Update a mod.
*
* #param Mod $mod A mod.
* #return Mod The mod.
*/
private function updateMod(Mod $mod): Mod {
$sql = 'UPDATE mods
SET
name = :name,
description = :description
WHERE
id = :id';
$statement = $this->connection->prepare($sql);
$statement->execute([
':name' => $mod->getName(),
':description' => $mod->getDescription(),
]);
return $mod;
}
/**
* Insert a mod.
*
* #param Mod $mod A mod.
* #return Mod The newly inserted mod.
*/
private function insertMod(Mod $mod): Mod {
$sql = 'INSERT INTO mods (
name,
description
) VALUES (
:name,
:description
)';
$statement = $this->connection->prepare($sql);
$statement->execute([
':name' => $mod->getName(),
':description' => $mod->getDescription(),
]);
$mod->setId(
$this->connection->lastInsertId()
);
return $mod;
}
}
Mvc/Domain/Model/Mod/Mod.php:
<?php
namespace Mvc\Domain\Model\Mod;
/**
* Mod entity.
*/
class Mod {
/**
* #param string|null $name (optional) A name.
* #param string|null $description (optional) A description.
*/
public function __construct(
private ?string $name = null,
private ?string $description = null
) {
}
/**
* Get id.
*
* #return int|null
*/
public function getId(): ?int {
return $this->id;
}
/**
* Set id.
*
* #param int|null $id An id.
* #return static
*/
public function setId(?int $id): static {
$this->id = $id;
return $this;
}
/**
* Get the name.
*
* #return string|null
*/
public function getName(): ?string {
return $this->name;
}
/**
* Set the name.
*
* #param string|null $name A name.
* #return static
*/
public function setName(?string $name): static {
$this->name = $name;
return $this;
}
/**
* Get the description.
*
* #return string|null
*/
public function getDescription(): ?string {
return $this->description;
}
/**
* Set the description.
*
* #param string|null $description A description.
* #return static
*/
public function setDescription(?string $description): static {
$this->description = $description;
return $this;
}
}
Mvc/Domain/Model/Mod/ModMapper.php:
<?php
namespace Mvc\Domain\Model\Mod;
use Mvc\Domain\Model\Mod\Mod;
/**
* An interface for various data mappers used to
* transfer Mod entities to and from a persistence system.
*/
interface ModMapper {
/**
* Check if a mod exists.
*
* #param Mod $mod A mod.
* #return bool True if the mod exists, false otherwise.
*/
public function modExists(Mod $mod): bool;
/**
* Save a mod.
*
* #param Mod $mod A mod.
* #return Mod The saved mod.
*/
public function saveMod(Mod $mod): Mod;
}

What's the best design for extra data on behaviour class?

I have a game where the player can finish some tasks.
I have separated the behaviour part of the task to its ORM part.
Eventually a copy of the task is being saved somewhere on the player's document (doesn't matter where for this specific question).
The problem is, I am not sure where to put the extra information that I send to the client that is not necessary for the behaviour itself, but it is needed to show the player information regarding the task itself.
This is my task interface:
interface ITask
{
/**
* #param Player $player
*/
public function init(Player $player);
/**
* #param PlayerAction $action
*/
public function progress(PlayerAction $action);
public function reset();
/**
* #return bool
*/
public function isComplete();
}
This is my abstract task:
abstract class BaseTask implements ITask
{
/**
* #var int
*/
public $id;
/**
* #var int
*/
protected $currentValue;
/**
* #var int
*/
protected $targetValue;
public function __construct($targetValue)
{
$this->currentValue = 0;
$this->targetValue = $targetValue;
}
/**
* #param int
*/
public abstract function setCurrentValue($current);
/**
* #return int
*/
public abstract function getCurrentValue();
/**
* #return int
*/
public abstract function getID();
/**
* #param int
*/
public abstract function setID($id);
/**
* #return int
*/
public abstract function getTargetValue();
/**
* #param int
*/
public abstract function setTargetValue($target);
/**
* #return boolean
*/
public function isComplete()
{
if ($this->getCurrentValue() >= $this->getTargetValue())
{
return true;
}
return false;
}
}
Now I need to decide how where to put the extra data, e.g description, title, theme etc...
I thought about two options: I can just put it on the base task
itself, but then what happens if I don't need it? I just leave it
blank? feel like the wrong place for me.
I could create a wrapper
class that will hold the task, but then I will need to always
call the wrapper to get to the task, and it feels kind of
wrong.
Looking for alternative suggestions.
You should inherit the CustomTask from TaskBase.
If you you have limitation in inheritance, encapsulate additional fields into a class called TaskAdditionalInfoBase and associate to the TaskBase.
Then various classes can inherit TaskAdditionalInfoBase to present a custom additional info to the the task.

How inform PhpStorm about method position used in DependencyInjection way, which it "thinks" that doesn't exist?

Is there any option to inform PhpStorm that method which it says that not exist, is beyond his scope and is defined somewhere else ?
In simpler words:
I have method execution:
Db::transactional($this)->transactionalUpdate($result);
I have got method definition also:
public function transactionalUpdate(ImportantObjectButNotMuch $baconWithButter)
{
echo 'Do a lot of tricks...';
}
Unfortunately PhpStorm doesn't know that execution : ->transactionalUpdate($result); should run public function transactionalUpdate.
Is there any option to write PhpDoc or some other tag to inform it that in case of name refactorization it should change the original function name too ?
P.S. My class structure looks like this:
class Db
{
public static function transactional($object)
{
return TransactionalProxy::newInstance($object); //3. It returns ApiObject object
}
}
class ApiObject
{
public function update_record()
{
//1. I am starting from there
$result = new ImportantObjectButNotMuch();
Db::transactional($this)->transactionalUpdate($result); //2. Next i am passing $this to Db class, to transactional method //4. It should run below transactionalUpdate method
}
public function transactionalUpdate(ImportantObjectButNotMuch $baconWithButter)
{
echo 'Do a lot of tricks...'; //5. It ends there, it is working but PhpStorm doesn't see it
}
}
EDIT AFTER ANSWER:
#Nukeface and #Dmitry caused me to come up with the answer on my Question:
Lets see again into my files structure:
class Db
{
public static function transactional($object)
{
return TransactionalProxy::newInstance($object); //3. It returns ApiObject object
}
}
class ApiObject
{
public function update_record()
{
//1. I am starting from there
$result = new ImportantObjectButNotMuch();
//EDIT//Db::transactional($this)->transactionalUpdate($result); //2. Next i am passing $this to Db class, to transactional method //4. It should run below transactionalUpdate method
/** #var self $thisObject */
//Line above informs PhpStorm that $thisObject is ApiObject indeed
$thisObject = Db::transactional($this)
$thisObject->transactionalUpdate($result);
}
public function transactionalUpdate(ImportantObjectButNotMuch $baconWithButter)
{
echo 'Do a lot of tricks...'; //5. It ends there, it is working but PhpStorm doesn't see it
}
}
You should make use of Typehints. Updated your code below:
/**
* Class Db
* #package Namespace\To\Db
*/
class Db
{
/**
* #param $object
* #return ApiObject (per your line comment)
*/
public static function transactional($object)
{
return TransactionalProxy::newInstance($object); //3. It returns ApiObject object
}
}
/**
* Class ApiObject
* #package Namespace\To\ApiObject
*/
class ApiObject
{
/**
* #return void (I see no "return" statement)
*/
public function update_record()
{
//1. I am starting from there
$result = new ImportantObjectButNotMuch();
Db::transactional($this)->transactionalUpdate($result); //2. Next i am passing $this to Db class, to transactional method //4. It should run below transactionalUpdate method
}
/**
* #param ImportantObjectButNotMuch $baconWithButter
* #return void
*/
public function transactionalUpdate(ImportantObjectButNotMuch $baconWithButter)
{
echo 'Do a lot of tricks...'; //5. It ends there, it is working but PhpStorm doesn't see it
}
}
You can quickly create basic docblocks and typehints by typing /** then pressing either "enter" or "space". Enter if you want a docblock and space if you want a typehint.
Examples of own code below:
/**
* Class AbstractEventHandler
* #package Hzw\Mvc\Event
*/
abstract class AbstractEventHandler implements EventManagerAwareInterface
{
/**
* #var EventManagerInterface
*/
protected $events;
/**
* #var EntityManager|ObjectManager
*/
protected $entityManager;
/**
* AbstractEvent constructor.
* #param ObjectManager $entityManager
*/
public function __construct(ObjectManager $entityManager)
{
$this->setEntityManager($entityManager);
}
/**
* #param EventManagerInterface $events
*/
public function setEventManager(EventManagerInterface $events)
{
$events->setIdentifiers([
__CLASS__,
get_class($this)
]);
$this->events = $events;
}
/**
* #return EventManagerInterface
*/
public function getEventManager()
{
if (!$this->events) {
$this->setEventManager(new EventManager());
}
return $this->events;
}
/**
* #return ObjectManager|EntityManager
*/
public function getEntityManager()
{
return $this->entityManager;
}
/**
* #param ObjectManager|EntityManager $entityManager
* #return AbstractEventHandler
*/
public function setEntityManager($entityManager)
{
$this->entityManager = $entityManager;
return $this;
}
}
In the above example, PhpStorm knows what every function requires and returns. It knows the types and as some "return $this" it knows about the possibility to chain functions.
As an addition, the above code example uses only "docblocks". Below some "inline typehints" from within a function. Especially useful when it's not going to be immediately clear what is going to be returned. That way, again, PhpStorm knows from where to get functions, options, etc. to show you.
/** #var AbstractForm $form */
$form = $this->getFormElementManager()->get($formName, (is_null($formOptions) ? [] : $formOptions));
/** #var Request $request */
$request = $this->getRequest();
As a final hint. If you create a bunch of properties for a class, such as in my example protected $events or protected $entityManager, you can also generate the getters & setters. If your properties contain the docblocks, it will also generate the docblocks for you on these functions.
E.g. the property below
/**
* #var EntityManager|ObjectManager
*/
protected $entityManager;
When using "Alt + Insert" you get a menu at cursor location. Choose "Getters/Setters". In the pop-up, select "entityManager" and check the box at the bottom for "fluent setters". Then the code below is generated for you:
/**
* #return ObjectManager|EntityManager
*/
public function getEntityManager()
{
return $this->entityManager;
}
/**
* #param ObjectManager|EntityManager $entityManager
* #return AbstractEventHandler
*/
public function setEntityManager($entityManager)
{
$this->entityManager = $entityManager;
return $this;
}
The closes thing you can do to what you want to do is to use #return with multiple types.
/**
* #param $object
* #return ApiObject|AnotherApiObject|OneMoreApiObject
*/
public static function transactional($object)
{
return TransactionalProxy::newInstance($object);
}

Properly Testing a simple class with Private Methods

Description:
I have a simple class that creates a symlink to a directory of uploaded files that are only available to registered members. It uses the current users session id in order to generate the random directory for the user. Once the user logs out, the symlink is removed. I would like to unit test the functionality of the class.
Question:
How do I go about properly unit testing this class since most functions are private, and I don't see any reason to make them public?
Here is the code for the PHP class:
<?php
namespace Test\BackEnd\MemberBundle\Library;
use Symfony\Component\HttpFoundation\Session\SessionInterface;
use Symfony\Component\HttpKernel\KernelInterface;
class DirectoryProtector
{
/** #var SessionInterface $_session */
private $_session;
/** #var ContainerInterface $_kernel */
private $_kernel;
/**
* #param SessionInterface $session
* #param KernelInterface $kernel
*/
public function __construct( SessionInterface $session, KernelInterface $kernel )
{
$this->_session = $session;
$this->_kernel = $kernel;
}
/**
* #param bool|false $protect
* Public method to symlink directories
*/
public function protectDirectory($protect = FALSE)
{
if ($protect) {
if ( ! $this->doesDirectoryExists())
symlink($this->getAppDir() . '/uploads', $this->getViewableSessionDirectory());
} else {
if ($this->doesDirectoryExists())
unlink($this->getViewableSessionDirectory());
}
}
/**
* #return bool
* Check to see if viewable session directory exists or not
*/
private function doesDirectoryExists()
{
if (file_exists($this->getViewableSessionDirectory()))
return TRUE;
return FALSE;
}
/**
* #return string
* Get viewable session full directory path
*/
private function getViewableSessionDirectory()
{
return $this->getAppDir() . '/../web/files/' . $this->getSessionId();
}
/**
* #return string
* Return app root directory
*/
private function getAppDir()
{
return $this->_kernel->getRootDir();
}
/**
* #return string
* Return session id
*/
private function getSessionId()
{
return $this->_session->getId();
}
}
Here is the code for the current test class:
<?php
namespace Test\BackEnd\MemberBundle\Tests\Library;
use Test\BackEnd\MemberBundle\Library\DirectoryProtector;
class DirectoryProtectorTest extends \PHPUnit_Framework_TestCase
{
public function testProtectDirectory()
{
//$this->markTestIncomplete("WIP on protect directory.");
$request = $this->getMockBuilder('Symfony\Component\HttpFoundation\Session\SessionInterface')
->getMock();
$container = $this->getMockBuilder('Symfony\Component\HttpKernel\KernelInterface')
->getMock();
/** #var DirectoryProtector $dp */
$dp = $this->getMockBuilder('Test\BackEnd\MemberBundle\Library\DirectoryProtector')
->setConstructorArgs(array($request, $container))
->setMethods(array(
'getViewableSessionDirectory',
'getAppDir',
'getSessionId'
))
->getMock();
$dp->expects($this->once())
->method('doesDirectoryExists')
->will($this->returnValue(TRUE));
$dp->protectDirectory(TRUE);
}
}
From https://phpunit.de/manual/current/en/test-doubles.html
Limitation: final, private, and static methods
Please note that final, private and static methods cannot be stubbed or mocked. They are ignored by PHPUnit's test double functionality and retain their original behavior.
Is not a good practice to unit test private or protected methods. You should test the public API. Private methods are supposed to be tested indirectly through the API. That said, you can make the method public with reflection:
$instance = new DirectoryProtector(...);
$ref = new \ReflectionClass('DirectoryProtector');
$method = $ref->getMethod('doesDirectoryExists');
$method->setAccessible(true);
$this->assertTrue($method->invoke($instance));
I think your approach is incorrect. You shouldn't mock method, but objects that are passed by injection in constructor.
For the SessionInterface you can pass MockArraySessionStorage. For ContainerInterface I don't know which class you are using, so I add some demo example. Test case:
public function testProtectDirectory()
{
$session = new MockArraySessionStorage();
$session->setId('123123');
$kernel = \Ouzo\Tests\Mock\Mock::create('ContainerInterface');
\Ouzo\Tests\Mock\Mock::when($kernel)->getRootDir()->thenReturn('/root_dir');
$directoryProtector = new DirectoryProtector($session, $kernel);
$directoryProtector->protectDirectory(true);
//asserts
}
and your methods will be returning:
getViewableSessionDirectory -> /root_dir/../web/files/123123
getAppDir -> /root_dir
And remember changing methods accessible is always bad idea.
PS. For mocking I use utils form Ouzo framework. Of course you can use other mocking framework.

PHPUnit Mockupbuilder passing return value correctly

I found some strange behavior with mockup builder, can someone explain to me why this happen?
here is my test code:
class PlaceTest extends \PHPUnit_Framework_TestCase
{
const API_KEY = 'test-api';
public function testConstruct()
{
$google = $this->getMockBuilder('GusDeCooL\GooglePhp\Google')
->setConstructorArgs(array(self::API_KEY))
->setMethods(array('getKey'))
->getMock();
$google->expects($this->any())
->method('getKey')
->will($this->returnValue(self::API_KEY));
/* #var $google \GusDeCooL\GooglePhp\Google */
$place = new Place($google);
$this->assertInstanceOf('GusDeCooL\GooglePhp\Component\Place\Place', $place);
$this->assertInstanceOf('GusDeCooL\GooglePhp\Google', $place->getParent());
$this->assertEquals(self::API_KEY, $place->getKey());
return $place;
}
/**
* #param Place $place
*
* #depends testConstruct
*/
public function testGetKey(Place $place)
{
$this->assertInstanceOf('GusDeCooL\GooglePhp\Google', $place->getParent());
$this->assertEquals(self::API_KEY, $place->getKey());
}
}
And here is the code of actual class
<?php
namespace GusDeCooL\GooglePhp\Component\Place;
use GusDeCooL\GooglePhp\Component\ChildInterface;
use GusDeCooL\GooglePhp\Google;
use GusDeCooL\GooglePhp\Place\Nearby;
class Place implements ChildInterface
{
/**
* #var Google
*/
private $parent;
/**
* #var Nearby
*/
private $nearby;
public function __construct(Google $parent)
{
$this->setParent($parent);
}
/**
* API Key
* #return string
*/
public function getKey()
{
return $this->getParent()->getKey();
}
}
While running the test, the PlaceTest::testConstruct() while doing $place->getKey() it pass the test but it errors in PlaceTest::testGetKey()
How is that happen?
It seems this is the limitation of mockup builder.
http://phpunit.de/manual/3.7/en/test-doubles.html
we can't pass mockup object into another test scope.
Please leave a comment if you found another reason.

Categories