Magento - UnitTests - Mock Objects - php

I am writing some tests for a Magento module, using Ivan Chepurnyi's extension, and I'm having trouble using the mock objects.
Here is the class:
<?php
class Namespace_Module_Block_Class extends Mage_Core_Block_Template
{
private $_salesCollection;
public function __construct()
{
$this->_salesCollection = Mage::getModel('module/classA')->getCollection()
->addFieldToFilter('id', $this->_getId());
}
public function _getId()
{
return Mage::getModel('module/classB')->getId();//session params
}
public function getSalesTotalNumber()
{
return $this->_salesCollection->count();
}
}
The method I'm trying to test is getSalesTotalNumber().
And here is the test:
<?php
class Namespace_Module_Test_Block_Class extends EcomDev_PHPUnit_Test_Case
{
private $_mock;
public function setUp()
{
$this->_mock = $this->getMock('Namespace_Module_Block_Class',
array('_getId')
);
$this->_mock->expects($this->any())
->method('_getId')
->will($this->returnValue(1024));
parent::setUp();
}
/**
* #test
* #loadFixture
* #loadExpectation
*/
public function testSalesTotalNumber()
{
$actual = $this->_mock->getSalesTotalValue();
$expected = $this->_getExpectations()->getSalesTotalNumber();
$this->assertEquals($expected, $actual);
}
}
As you can see, what I want to do is overwrite the _getId() method so that it returns an id which match the id in the fixture and so load the collection. But it doesn't work :-(.
In my test, if I echo $this->_mock->_getId() it returns the correct Id (1024). But in the __construct() of my class $this->_getId() returns null, which is the expected value during testing (I mean, during testing there is no session, so it can't get the object's Id as I store it in a session variable). So the _getId() method isn't mocked by my test case.
Any help will be highly appreciated.

So my problem was not in the mock/test but in the class.
I have moved the content of __construct() into a protected method which returns the collection object. That's how my class looks like now:
<?php
class Namespace_Module_Block_Class extends Mage_Core_Block_Template
{
private $_salesCollection;
protected function _getAffiliateSales()
{
if (is_null($this->_salesCollection)) {
$affiliateId = $this->_getId();
$this->_salesCollection = Mage::getModel('module/classA')
->addFieldToFilter('id', $affiliateId);
}
return $this->_salesCollection;
}
public function _getId()
{
return Mage::getModel('module/classB')->getId();//session params
}
public function getSalesTotalNumber()
{
return $this->_getAffiliateSales()->count();
}
}

Related

PHP cast object in simple ORM

I would like to make a simple ORM in PHP for standard CRUD interaction with my db, I also want make it work in php5 for legacy compatibility.
I've written some classes to do this and it works, but not completely as I would.
This is the idea. I have an abstrac class called ModelBase which has a property (tableName) and some metods like select, insert, update and delete, plus has an abstract method, getData, that will be implemented by the classes that will be implement ModelBase and should return object of correct type.
So, for example, I could have a class Users which implements ModelBase and one another class UserData which is the model with the property.
Here is the code:
abstract class ModelBase{
private $tableName;
public function __construct($tableName) {
$this->tableName = $tableName;
}
public function select{
// make select query to db and retreive data
// ...
$resData = [];
while($dataRow = mysqli_fetch_array($res, MYSQLI_ASSOC)) {
$resData[] = $this->getObjectData($dataRow); // implemented in child class
}
return $resData;
}
public function insert(){ /* ... */}
public function update(){ /* ... */}
public function delete(){ /* ... */}
abstract function getObjectData($data); // maps the results
}
class UserData {
public $id;
public $name;
public $surname;
public $email;
// other fields
public function __construct() {}
}
class User implements ModelBase {
private $tableName = 'users';
public function __construct() {
parent::__construct($this->tableName);
}
public function getObjectData($dataRow) {
$o = new UserData ();
// mapping dataRow to object fields
$o->id = $dataRow['ID'];
// ....
return $o;
}
}
So I use my classes in this way:
$users = new Users();
$u = users->select();
$firstUser = $u[0]; // I get my user if exists
In $firstUser I'll get my object with property and correct data but I would like to have that also my IDE (vsCode in this case) would recognize the object type in order to suggest the correct properties. So if I write $firstUser-> I would like to see field suggestions (id, name, surname, ...) from UserData and for other xyzData classes as well.
What I should do to improve my classes in order to see property suggestions when I use my objects, also in php5?
Solution for PHP 8, tested on PHPStorm.
<?php
class Base {
/**
* #return static[]
*/
public function select() : array {
return [new self];
}
public function selectFirst() : static {
return $this->select()[0];
}
}
class User extends Base {
public ?string $userName = null;
}
#detects the current class via () : static
(new User)->selectFirst()->userName;
#detects the current class via #return static[]
(new User)->select()[0]->userName;
In line solution for PHP 5, define the variable directly with this comment
/** #var $a User */
$a->userName;
There is no benefit of supporting old PHP 5. You lose so mutch clean code and modern approach when supporting old php versions.
But when you have to, then go with the inline solution.
Not tested and not so clean for PHP 5:
class User extends Base {
public ?string $userName = null;
/**
* #return User[]
*/
public function select() : array {
return parent::select();
}
}

Can't access construct variable in method ( php laravel )

I have a base class which sets up's other extending controllers like this:
class BaseController extends Controller
{
public $globalCurrencies;
public $globalLanguages;
public function __construct()
{
$this->globalCurrencies = $this->getCurrencies(); // this works
$this->globalLanguages = $this->getLanguages(); // this works
}
}
And I use one of helpers to extend this class like this:
class SessionHelper extends BaseController
{
public $test;
public function __construct()
{
parent::__construct(); // fire parent aka basecontroller construct
$this->test = $this->globalCurrencies; // this works (variables are set)
echo '__construct: '.$this->test; // this even displays it
}
public function getCurrencies()
{
dd('method'.$this->test); // NOT WORKING
}
public function getCurrentCurrency()
{
return $this->getCurrencies()->where('id', Session::get('currencyId'))->first() ?? null;
}
}
Later on code is used in model:
class Product extends Model
{
protected $table = "products";
public $timestamps = true;
public $sessionHelper;
public function __construct()
{
$this->sessionHelper = new SessionHelper;
}
public function getPrice($conversion_rate = null)
{
return number_format($this->price_retail / $this->sessionHelper->getCurrentCurrency()->conversion_rate, 2);
}
}
Have any body idea why I can access in construct variable but not in method? If i remember correctly construct is fired first so everything after should have access to it.
Declare $test variable as private out side the constructor. Inside the constructor keep it the way you are doing it right now and then make a setter and getter for the test variable.
class testObject
{
private $test;
function __construct($test)
{
$this->test= $this->globalCurrencies;
}
// test getter
function getTest()
{
return $this->test;
}
}
Change your method to be;
public function getCurrencies()
{
dd('method', $this->test);
}
You can not concatenate strings and objects/arrays.
If that doesn't resolve the issue - check the laravel.log

Mock method from the same class that tested method is using

I have following code:
class Foo() {
public function someMethod() {
...
if ($this->otherMethod($lorem, $ipsum)) {
...
}
...
}
}
and I'm trying to test the someMethod(), I don't want to test otherMethod() since it's quite complex and I have dedicated tests - here I would only like to mock it and return specific values.
So I tried to:
$fooMock = Mockery::mock(Foo::class)
->makePartial();
$fooMock->shouldReceive('otherMethod')
->withAnyArgs()
->andReturn($otherMethodReturnValue);
and in test I'm calling
$fooMock->someMethod()
But it's using the original (not mocked) method otherMethod() and prints errors.
Argument 1 passed to Mockery_3_Foo::otherMethod() must be an instance of SomeClass, boolean given
Could you help me please?
Use this as a template to mock a method:
<?php
class FooTest extends \Codeception\TestCase\Test{
/**
* #test
* it should give Joy
*/
public function itShouldGiveJoy(){
//Mock otherMethod:
$fooMock = Mockery::mock(Foo::class)
->makePartial();
$mockedValue = TRUE;
$fooMock->shouldReceive('otherMethod')
->withAnyArgs()
->andReturn($mockedValue);
$returnedValue = $fooMock->someMethod();
$this->assertEquals('JOY!', $returnedValue);
$this->assertNotEquals('BOO!', $returnedValue);
}
}
class Foo{
public function someMethod() {
if($this->otherMethod()) {
return "JOY!";
}
return "BOO!";
}
public function otherMethod(){
//In the test, this method is going to get mocked to return TRUE.
//that is because this method ISN'T BUILT YET.
return false;
}
}

How to extend a Laravel vendor package

I am trying to extend Kryptonit3/Counter. Particularly, I need to overwrite one private function inside the class Counter.php to retrieve only hits for the last 24 hours.
Counter.php private function:
private static function countHits($page)
{
$page_record = self::createPageIfNotPresent($page);
return number_format($page_record->visitors->count());
}
The function I need:
private static function countHits($page)
{
$page_record = self::createPageIfNotPresent($page);
return number_format($page_record->visitors()->where('created_at', '>=', Carbon::now()->subDay())->count());
}
Therefore, I am looking for the right way to overwrite this package.
Approach 1: Should I create my own class extending Counter.php and including my custom function in this class? If so, what happens with the private classes included in the original class? Should I create a service provider? How this service provider would look like?
Approach 2: Should I fork the vendor package and update this package to my own need?
I already looked at all stackoverflow questions related to this topic but they are not clear enough.
UPDATE:
This is what I did so far:
Create MyCounter class:
<?php
namespace App\Helpers\Counter;
use Kryptonit3\Counter\Counter;
class MyCounter extends Counter
{
}
Create MyCounterServiceProvider:
<?php
namespace App\Providers;
use App\Helpers\MyCounter;
use Illuminate\Support\ServiceProvider;
class MyCounterServiceProvider extends ServiceProvider
{
/**
* Bootstrap the application services.
*
* #return void
*/
public function boot()
{
//
}
/**
* Register the application services.
*
* #return void
*/
public function register()
{
$this->app['counter'] = $this->app->share(function($app)
{
$visitor = $app['visitor'];
return new MyCounter($visitor);
});
}
}
Create MyCounterFacade:
<?php
namespace App\Helpers\Counter;
use Illuminate\Support\Facades\Facade;
class MyCounterFacade extends Facade
{
protected static function getFacadeAccessor() { return 'mycounter'; }
}
Include the provider and the alias inside config/app.php:
App\Providers\MyCounterServiceProvider::class,
and
'MyCounter' => App\Helpers\Counter\MyCounterFacade::class,
Problem was related with MyCounterServiceProvider. The next piece of code solved the problem.
public function register()
{
$this->app->singleton('mycounter', function() {
return $this->app->make('App\Helpers\Counter\MyCounter');
});
}
Static methods cannot be overriden statically but you can use __callstatic to override them dinamically:
Route::get('/override', function() {
$b = new ClassB();
dd($b::originalMethod());
});
class ClassA {
private static function originalMethod() {
return 'Original value from ClassA';
}
public function callingOriginalMethodMethod()
{
return static::originalMethod();
}
}
class ClassB {
public static function __callStatic($name, $arguments) {
if ($name == 'originalMethod') {
return static::overloadedMethod();
}
return forward_static_call_array(array(ClassA::class, $name), $arguments);
}
protected static function overloadedMethod() {
return 'Overloaded value from ClassB';
}
}
Hit /override and you should see
Overloaded value from ClassB
That being said, what you could do:
Create an override for that class:
<?php
namespace App\Helpers;
use Kryptonit3\Counter\Counter;
class MyCounter extends Counter
{
public static function __callStatic($name, $arguments) {
if ($name == 'countHits') {
return static::myCountHits($page);
}
return forward_static_call_array(array(Counter::class, $name), $arguments);
}
protected static function myCountHits($page) {
return 'whatever';
}
}
Then you just have to override the original instance of counter.
app()->singleton('counter', function() {
return app()->make(MyCounter::class);
});

Use mockery to unit test a class with dependencies

I'm new to testing and I am trying to create a unit test that covers the first if statement in the NewsCreator create method.
This question has two parts.
First: How should I be instantiating NewsCreator to handle the mocked validator and repository?
Second: What would the correct way to test this path be?
Here is my controller method that calls the class that needs testing:
public function store()
{
$creator = new NewsCreator($this);
return $creator->create(Input::all());
}
Here is the class that I wish to test, NewsCreator:
<?php namespace WHS\Portal\News;
class NewsCreator {
protected $listener;
protected $repository;
protected $validator;
protected $errors;
public function __construct($listener, NewsRepositoryInterface $repository, NewsValidator $validator)
{
$this->listener = $listener;
$this->repository = $repository;
$this->validator = $validator;
$this->errors = [];
}
public function create($data)
{
if($this->validator->fails($data))
{
return $this->listener->newsCreationFails($this->validator->messages());
}
if($this->repository->create($data))
{
return $this->listener->newsCreationSucceeds();
}
return $this->listener->newsCreationFails($this->errors);
}
}
This is the test I attempted to write, but it fails
with exception:
2) WHS\Portal\Tests\News\NewsCreatorTest::test_failed_validation Mockery\Exception\InvalidCountException: Method fails("foo") from Mockery_1_WHS_Portal_News_NewsValidator should be called exactly 1 times but called 0 times.
<?php namespace WHS\Portal\Tests\News;
use TestCase;
use Mockery as m;
class NewsCreatorTest extends TestCase {
public function tearDown()
{
m::close();
}
public function test_failed_validation()
{
$newsRepo = m::mock('\WHS\Portal\News\DbNewsRepository["create"]');
$newsValidator = m::mock('\WHS\Portal\News\NewsValidator["fails"]');
$listener = new NewsListenerStub();
$listener = m::mock($listener)->makePartial();
$newsValidator->shouldReceive('fails')->with('foo')->once()->andReturn(true);
$listener->shouldReceive('newsCreationFails')->once()->with('foo')->andReturn();
$newsCreator = new \WHS\Portal\News\NewsCreator($listener,$newsRepo,$newsValidator);
$newsCreator->create([]);
}
}
Updated test:
use TestCase;
use Mockery as m;
class NewsCreatorTest extends TestCase {
public function tearDown()
{
m::close();
}
public function test_failed_validation()
{
$newsRepo = m::mock('\WHS\Portal\News\DbNewsRepository["create"]');
$newsValidator = m::mock('\WHS\Portal\News\NewsValidator["fails"]');
$listener = m::mock('\WHS\Portal\Tests\News\NewsListenerStub["newsCreationFails"]');
$newsValidator->shouldReceive('fails')->with([])->once()->andReturn(true);
$newsValidator->shouldReceive('messages')->once();
$listener->shouldReceive('newsCreationFails')->once()->with('foo')->andReturn('foo-bar');
$newsCreator = new \WHS\Portal\News\NewsCreator($listener,$newsRepo,$newsValidator);
$result = $newsCreator->create([]);
$this->assertEquals('foo-bar', $result);
}
}
The stub class:
class NewsListenerStub
{
public function newsCreationFails($data)
{
return $data;
}
}
Please help.
Thanks.
The method fails() is called with the $data argument in your class.
In your unit test your are passing in an empty array as data create([]).
Your argument expectation on the validatorMock is expecting fails() to be called with the parameter foo. You have to alter that to match the empty array.
$newsValidator->shouldReceive('fails')->with([])->once()->andReturn(true);
Also you have to specify the validator->messages() method on the validatorMock because that is also being called in your class.
$newsValidator->shouldReceive('messages')->once();
For this test to really make sence you have to assert that the result of NewsCreationFails matches the return value of create().
$listener->shouldReceive('newsCreationFails')->once()->with('foo')->andReturn('foo-bar');
...
$result = $newsCreator->create([]);
$this->assertEquals('foo-bar', $result);

Categories