I need to mock Laravel's Eloquent\Model with Mockery and it is kind of tricky because it uses static methods.
I solved this issue with the following code but I wonder if there is a better/smarter way to do this.
<?php
use Ekrembk\Repositories\EloquentPostRepository;
class EloquentPostRepositoryTest extends TestCase {
public function __construct()
{
$this->mockEloquent = Mockery::mock('alias:Ekrembk\Post');
}
public function tearDown()
{
Mockery::close();
}
public function testTumuMethoduEloquenttenAldigiCollectioniDonduruyor()
{
$eloquentReturn = 'fake return';
$this->mockEloquent->shouldReceive('all')
->once()
->andReturn($eloquentDongu);
$repo = new EloquentPostRepository($this->mockEloquent);
$allPosts = $repo->all();
$this->assertEquals($eloquentReturn, $allPosts);
}
}
Tough to tell without the source of "Ekrembk\Repositories\EloquentPostRepository", but, I see a couple of issues. It looks like within your EloquentPostRepository, you're calling a static. You shouldn't do that. It makes it hard to test (as you've discovered). Assuming that Ekrembk\Post extends from Eloquent, you can do this instead:
<?php
namespace Ekrembk\Repositories
class EloquentPostRepository {
protected $model;
public __construct(\Ekrembk\Post $model) {
$this->model = $model;
}
public function all()
{
$query = $this->model->newQuery();
$results = $query->get();
return $results;
}
}
Then, your test will be simpler:
<?php
use Ekrembk\Repositories\EloquentPostRepository;
class EloquentPostRepositoryTest extends TestCase {
public function tearDown()
{
Mockery::close();
}
public function testTumuMethoduEloquenttenAldigiCollectioniDonduruyor()
{
$mockModel = Mockery::mock('\Ekrembk\Post');
$repo = new EloquentPostRepository($mockModel);
$eloquentReturn = 'fake return';
$mockModel->shouldReceive('newQuery')->once()->andReturn($mockQuery = m::mock(' \Illuminate\Database\Eloquent\Builder'));
$result = $mockQuery->shouldReceive('get')->once()->andReeturn($eloquentReturn);
$this->assertEquals($eloquentReturn, $result);
}
}
Didn't test the above so it might have issues, but you should get the idea.
If you look in Illuminate\Database\Eloquent\Model, you'll see that "public static function all" is really just calling "get" on an instantiated eloquent model.
Related
I'm trying to understand how WordPress works with actions, classes, and methods.
If there is a class "TestClass" and it has a public method 'method1'
The method can be hooked to any action as "add_action('theHook', ['TestClass', 'method1']);"
From my understanding. If you don't initialize the class, you can not access its public methods and objects. Now, I would assume that WordPress has to follow this, and it must initialize my "TestClass", which will cause for public __construct() to fire.
However, after testing this, it does not fire __construct()..
Why is this?. I know a fix would be to self initialize inside 'method1', but I'm trying to figure out why WordPress behaves this way.
Because WordPress call your method as a static function: TestClass::method()
There is various solution:
1. Init class before add Action
Initialize your class before add action, like that:
$test = new TestClass();
add_action('hook', [$test, 'method']);
2. Call hook inside your Class:
class TestClass {
public function __construct() {
// Your construct
}
public function method() {
// Your Method
}
public function call_hook() {
add_action('hook', [$this, 'method']);
}
}
$test = new TestClass();
$test->call_hook();
3. Use a singleton
And if you need to to have only one instance of your class and call it in various place, you have to take a look to Singleton design pattern.
Demonstration:
class MySingletonClass {
private static $__instance = null;
private $count = 0;
private function __construct() {
// construct
}
public static function getInstance() {
if (is_null(self::$__instance)) {
self::$__instance = new MySingletonClass();
}
return self::$__instance;
}
public function method() {
$this->count += 1;
error_log("count:".$this->count);
}
}
$singleton = MySingletonClass::getInstance();
add_action('wp_head', [$singleton, 'method']);
$singleton2 = MySingletonClass::getInstance();
add_action('wp_footer', [$singleton2, 'method']);
I am trying to simplify my applications dependency injection by creating a base injection class.
So far most of the code works fine, except for registerForAutoconfiguration
Here is the relevant code:
abstract class AbstractTaggedPass implements CompilerPassInterface
{
protected $interfaceClass;
protected $serviceClass;
protected $tag;
protected $method;
public function process(ContainerBuilder $container)
{
// always first check if the primary service is defined
if (!$container->has($this->serviceClass)) {
return;
}
// Register classes implementing the interface with tag
$container->registerForAutoconfiguration($this->interfaceClass)->addTag($this->tag); // Does not work
$definition = $container->findDefinition($this->serviceClass);
// find all service IDs with the tag
$taggedServices = $container->findTaggedServiceIds($this->tag);
foreach ($taggedServices as $id => $tags) {
foreach ($tags as $attributes) {
$definition->addMethodCall($this->method, [new Reference($id)]);
}
}
}
}
class SubscriptionPaymentProviderPass extends AbstractTaggedPass
{
protected $interfaceClass = SubscriptionPaymentProviderInterface::class
protected $serviceClass = SubscriptionPaymentProviderPool::class;
protected $tag = 'subscription.payment_provider';
protected $method = 'addProvider';
}
class SubscriptionBundle extends Bundle
{
protected function getContainerExtensionClass()
{
return SubscriptionExtension::class;
}
public function build(ContainerBuilder $container)
{
parent::build($container);
//$container->registerForAutoconfiguration(SubscriptionPaymentProviderInterface::class)->addTag('subscription.payment_provider');
$container->addCompilerPass(new SubscriptionPaymentProviderPass());
}
}
If I move registerForAutoconfiguration line from Bundle class into the CompilerPass class, then it no longer registers Services with the correct tag.
Is it possible to use it inside a compiler pass?
Do I need to enable something to make it work?
Compiler Pass is used after service definitions are parsed (via configuration file or extensions).
I think the right place for do this, is into an Extension.
Symfony 2.8.13 / Doctrine ORM 2.5.5 / PHPUnit 5.7.5
I want to test a method of a class that makes use of the doctrine entity manager. This public method calls a private one that instantiates a Bookmark entity, flushes it and returns this entity. Then later, in the tested method I need to access the entity Id. Everything is mocked excepted the Bookmark entity itself. The main problem is that there is no setId() method in my entity. Here is the code and my main idea to solve this issue but I don't know if it is correct ?
Tested class and method
class BookmarkManager
{
//...
public function __construct(TokenStorageInterface $tokenStorage, ObjectManager $em, Session $session)
{
//...
}
public function manage($bookmarkAction, $bookmarkId, $bookmarkEntity, $bookmarkEntityId)
{
//...
$bookmark = $this->add($bookmarkEntity, $bookmarkEntityId);
//...
$bookmarkId = $bookmark->getId();
//...
}
private function add($entity, $entityId)
{
//...
$bookmark = new Bookmark();
//...
$this->em->persist($bookmark);
$this->em->flush();
return $bookmark;
}
}
Test
class BookmarkManagerTest extends \PHPUnit_Framework_TestCase
{
public function testThatRestaurantAdditionToBookmarksIsWellManaged()
{
//...
// THIS WON'T WORK AS NO setId() METHOD EXISTS
$entityManagerMock->expects($this->once())
->method('persist')
->will($this->returnCallback(function ($bookmark) {
if ($bookmark instanceof Bookmark) {
$bookmark->setId(1);
}
}));
//...
$bookManager = new BookmarkManager($tokenStorageMock, $entityManagerMock, $sessionMock);
//...
}
}
Solutions ?
1- Make usage of reflection class as proposed here :
$entityManagerMock->expects($this->once())
->method('persist')
->will($this->returnCallback(function ($bookmark) {
if ($bookmark instanceof Bookmark) {
$class = new \ReflectionClass($bookmark);
$property = $class->getProperty('id');
$property->setAccessible(true);
$property->setValue($bookmark, 1);
//$bookmark->setId(1);
}
}));
2- Create a test Boookmark entity that extends from the real one and add a setId() method. Then create a mock of this class and replace and customize the one got from the ReturnCallback method with this one ? It seems crappy...
Any thoughts ? Thanks for your help.
The reflection looks interesting but it decreases readability of tests (mixing with mocks makes the situation tough).
I would create a fake for entity manager and implements there setting id based on reflection:
class MyEntityManager implements ObjectManager
{
private $primaryIdForPersitingObject;
public function __construct($primaryIdForPersitingObject)
{
$this->primaryIdForPersitingObject = $primaryIdForPersitingObject;
}
...
public function persist($object)
{
$reflectionClass = new ReflectionClass(get_class($object));
$idProperty = $reflectionClass->getProperty('id');
$idProperty->setAccessible(true);
$idProperty->setValue($object, $this->primaryIdForPersitingObject);
}
public function flush() { }
...
}
Once you implemented this, you can inject the instance of MyEntityManager and make your tests small and easier to maintain.
You test would look like
<?php
class BookmarkManagerTest extends \PHPUnit_Framework_TestCase
{
public function testThatRestaurantAdditionToBookmarksIsWellManaged()
{
// ...
$entityManager = MyEntityManager(1);
//...
$bookManager = new BookmarkManager($tokenStorageMock, $entityManager, $sessionMock);
//...
}
}
Of course, a situation may be harder if there is a need of setting different ids for many persisting objects. Then you can, for example, increase $primaryIdForPersitingObject on persist call
public function persist($object)
{
$reflectionClass = new ReflectionClass(get_class($object));
$idProperty = $reflectionClass->getProperty('id');
$idProperty->setAccessible(true);
$idProperty->setValue($object, $this->primaryIdForPersitingObject);
$this->primaryIdForPersitingObject++;
}
It may be extended even further to have separate primaryIdForPersitingObject each entity class, and your tests will be still clean.
Connect.php
class Connect
{
private $dbName;
public function __construct($dbName)
{
$this->dbName = $dbName;
}
public function getQuery()
{
return $this->dbName;
}
}
ConnectFacade.php
class ConnectFacade
{
public $dbA;
public $dbB;
public $dbC;
public $dbD;
public function __construct()
{
$this->dbA = new Connect('connect to database a');
$this->dbB = new Connect('connect to database b');
$this->dbC = new Connect('connect to database c');
$this->dbD = new Connect('connect to database d');
}
}
Member.php
class Member
{
private $connect;
public function __construct(ConnectFacade $connect)
{
$this->connect = $connect;
}
public function data()
{
return $this->connect->dbA->getQuery();
}
}
index.php
require 'vendor/autoload.php';
echo (new Member(new ConnectFacade))->data();
In my case, I have many tables from many databases need to be connected in one class, therefore I need to declare many connections with different connection name very often, so I put it to ConnectFacade for saving time, I know it's kind of complicated, but that's the structure of my company, this sample code will show connect to database a on index.php, of course in reality the connection is doing a lot of stuffs, my question is I don't know how mock on this situation, this is what I tried
use Mockery as m;
class MemberTest extends PHPUnit_Framework_TestCase
{
public function testData()
{
$connect = m::mock('ConnectFacade');
$connect->shouldReceive('dbA->getQuery')
->once()
->andReturn(true);
$actual = (new Member($connect))->data();
$this->assertTrue($actual);
}
public function tearDown()
{
m::close();
}
}
and I got Call to a member function getQuery() on a non-object in D:\www\phpunit\class\Member.php on line 14, I don't know how to make the test pass.
The problem is that dbA is not a method on ConnectFacade, but rather a variable. Therefore $connect->shouldReceive('dbA->getQuery') won't work.
Instead you need to set the dbA variable as a mock and then change the expectation to just receive getQuery:
public function testData()
{
$connect = m::mock('ConnectFacade');
$connect->dbA = $connect;
$connect->shouldReceive('getQuery')
->once()
->andReturn(true);
$actual = (new Member($connect))->data();
$this->assertTrue($actual);
}
Another option would be to instead mock the creation of the Connect class in ConnectFacade instead of mocking the whole ConnectFacade:
public function testData()
{
$connect = m::mock('overload:Connect');
$connect->shouldReceive('getQuery')
->once()
->andReturn(true);
$connectFacade = new ConnectFacade;
$actual = (new Member($connectFacade))->data();
$this->assertTrue($actual);
}
I have an abstract class that has common methods in it, that I wish to test, so I do not have to keep testing them in each class that extends this class.
abstract class Class1 implements iClass1
{
const VALUE = 'A';
private $Return;
public function __construct($Field = NULL)
{
if( ! is_null($Field) )
$this->SetField($Field);
}
public function GetField()
{
return $this->Return;
}
public function SetField($Field)
{
if (strlen($Field) != 3)
throw new CLASS1_EXCEPTION('Field "' . $Field . '" must be 3 digits.');
$this->Return = $FieldCode;
}
abstract function CalculateData();
}
I want to create the basic test case then that will test the constructor, and the GetField and other functions, then my other test files can test the abstract functions.
I want to be able to test the const has not changed, the field throws the exception etc...
TEST:
class TEST_CLASS1 extends PHPUnit_Framework_TestCase
{
protected function setUp()
{
require_once('CLASS1.php');
}
public function testConstants()
{
$this->assertEquals(CLASS1, 'A');
}
/* #expectedException CLASS1_EXCEPTION
public function testLargeFieldException()
{
$class1 = new CLASS1('ABCD');
$class1 = new CLASS1();
$class1->SetField('ABCD');
}
}
How do I create the tests since I can not create the CLASS1 object as it is an abstract class?
One option is to create a
TestableClass1 extends Class1 {
public function CalculateData() {}
}
and use that class for your tests.
The other option is to do pretty much the same but use an API phpunit provides you with:
For this see the sample Example 10.13: Testing the concrete methods of an abstract class of the phpunit documentation:
A simpler example:
abstract class AbstractClass
{
public function concreteMethod()
{
return 5;
}
public abstract function abstractMethod();
}
class AbstractClassTest extends PHPUnit_Framework_TestCase
{
public function testConcreteMethod()
{
$sut = $this->getMockForAbstractClass('AbstractClass');
$this->assertSame(5, $sut->concreteMethod());
}
}