Skip to content

Commit

Permalink
add MockTrait::emCreateMockEntity and tests for MockTrait
Browse files Browse the repository at this point in the history
  • Loading branch information
tflori committed Jun 18, 2017
1 parent 6cdf4ea commit 4eb8903
Show file tree
Hide file tree
Showing 8 changed files with 337 additions and 31 deletions.
35 changes: 35 additions & 0 deletions docs/testing.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,41 @@ abstract class TestCase extends MockeryTestCase
}
```

### Create a Partial Mock Entity

Due to the fact that the entity constructor is final and calls an internal method (`onInit()`) you can not just create
the mock with `Mockery::mock(Article::class, [['id' => 42, 'title' => 'Hello World!']])->makePartial()`. Also creating
a passive mock with `Mockery::mock(new Article(['id' => 42, 'title' => 'Hello world!']))` does not work because
the orignal object is wrapped but the magic getter not.

You have to initialize the object without using the original constructor, set the entity manager manually, set the
original data and reset the entity. We created a helper for this to make it easier.

```php
<?php

class ArticleControllerTest extends TestCase
{
public function testMockEntity()
{
$entity = \Mockery::mock(Article::class)->makePartial();
$entity->setEntityManager($this->mocks['em']);
$entity->setOriginalData(['id' => 42, 'title' => 'Hello World!']);
$entity->reset;

// now you can use the entity as usual and write expectations
}

public function testMockEntityUsingHelper()
{
$entity = $this->emCreateMockEntity(Article::class, ['id' => 42, 'title' => 'Hello World!']);
// when onInit is required call it now: $entity->onInit(false);

// now you can use the entity as usual and write expectations
}
}
```

### Mocking Create, Read, Update and Delete (CRUD)

First we want to have a look on the basics- so lets have a look on CRUD-Operations.
Expand Down
3 changes: 0 additions & 3 deletions phpunit.xml
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,6 @@
<filter>
<whitelist processUncoveredFilesFromWhitelist="true">
<directory suffix=".php">./src/</directory>
<exclude>
<file>./src/MockTrait.php</file>
</exclude>
</whitelist>
</filter>
</phpunit>
2 changes: 1 addition & 1 deletion src/EntityManager.php
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ public static function getInstance($class = null)

if (!isset(self::$emMapping['byClass'][$class])) {
if (!($em = self::getInstanceByParent($class)) && !($em = self::getInstanceByNameSpace($class))) {
$em = self::$emMapping['last'];
return self::$emMapping['last'];
}

self::$emMapping['byClass'][$class] = $em;
Expand Down
67 changes: 40 additions & 27 deletions src/MockTrait.php
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,24 @@ public function emInitMock($options = [], $driver = 'mysql')
return $em;
}

/**
* Create a partial mock of Entity $class
*
* @param string $class
* @param array $data
* @param EntityManager $em
* @return Mock|Entity
*/
public function emCreateMockedEntity($class, $data = [], $em = null)
{
/** @var Entity|Mock $entity */
$entity = \Mockery::mock($class)->makePartial();
$entity->setEntityManager($em ?: EntityManager::getInstance($class));
$entity->setOriginalData($data);
$entity->reset();
return $entity;
}

/**
* Expect an insert for $class
*
Expand All @@ -61,30 +79,28 @@ public function emExpectInsert($class, $defaultValues = [], $em = null)
/** @var EntityManager|Mock $em */
$em = $em ?: EntityManager::getInstance($class);

if (!is_callable([$em, 'shouldReceive'])) {
throw new Exception('EntityManager mock not initialized');
}

$em->shouldReceive('sync')->with(m::type($class))->once()
->andReturnUsing(function (Entity $entity, $reset = false) {
$entity->getPrimaryKey(); // this may throw
return false;
});
->andReturnUsing(function (Entity $entity, $reset = false) use ($class, $defaultValues, $em) {
$expectation = $em->shouldReceive('insert')->once()
->andReturnUsing(function (Entity $entity, $useAutoIncrement = true) use ($defaultValues, $em) {
if ($useAutoIncrement && !isset($defaultValues[$entity::getPrimaryKeyVars()[0]])) {
$defaultValues[$entity::getPrimaryKeyVars()[0]] = mt_rand(1, pow(2, 31) - 1);
}
$entity->setOriginalData(array_merge($defaultValues, $entity->getData()));
$entity->reset();
$em->map($entity);
return true;
});

$em->shouldReceive('insert')->with(m::on(function ($entity, $useAutoIncrement = true) use ($class) {
if ($entity instanceof $class) {
return true;
}
return false;
}))->once()->andReturnUsing(function (Entity $entity, $useAutoIncrement = true) use ($defaultValues, $em) {
if ($useAutoIncrement && !isset($defaultValues[$entity::getPrimaryKeyVars()[0]])) {
$defaultValues[$entity::getPrimaryKeyVars()[0]] = mt_rand(1, pow(2, 31) - 1);
}
$entity->setOriginalData(array_merge($defaultValues, $entity->getData()));
$entity->reset();
$em->map($entity);
return true;
});
try {
$entity->getPrimaryKey();
$expectation->with(m::type($class), false);
return false;
} catch (IncompletePrimaryKey $ex) {
$expectation->with(m::type($class));
throw $ex;
}
});
}

/**
Expand All @@ -103,15 +119,12 @@ public function emExpectFetch($class, $entities = [], $em = null)
/** @var EntityManager|Mock $em */
$em = $em ?: EntityManager::getInstance($class);

if (!is_callable([$em, 'shouldReceive'])) {
throw new Exception('EntityManager mock not initialized');
}

$fetcher = \Mockery::mock(EntityFetcher::class, [$em, $class])->makePartial();
$em->shouldReceive('fetch')->with($class)->once()->andReturn($fetcher);

$fetcher->shouldReceive('one')->with()->andReturnValues($entities)->byDefault();
$fetcher->shouldReceive('count')->with()->andReturn(count($entities))->byDefault();
array_push($entities, null);
$fetcher->shouldReceive('one')->with()->andReturnValues($entities)->byDefault();

return $fetcher;
}
Expand Down
34 changes: 34 additions & 0 deletions tests/MockTrait/CreateMockedEntityTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<?php

namespace ORM\Test\MockTrait;

use ORM\MockTrait;
use ORM\Test\Entity\Examples\Article;
use ORM\Test\TestCase;

class CreateMockedEntityTest extends TestCase
{
use MockTrait;

protected $em;

protected function setUp()
{
$this->em = $this->emInitMock();
}

public function testReturnsTheEntity()
{
$article = $this->emCreateMockedEntity(Article::class);

self::assertInstanceOf(Article::class, $article);
}

public function testSetsTheData()
{
$article = $this->emCreateMockedEntity(Article::class, ['id' => 42, 'title' => 'Hello World!']);

self::assertSame(42, $article->id);
self::assertSame('Hello World!', $article->title);
}
}
76 changes: 76 additions & 0 deletions tests/MockTrait/ExpectFetchTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
<?php

namespace ORM\Test\MockTrait;

use Mockery\Adapter\Phpunit\MockeryTestCase;
use Mockery\MockInterface;
use ORM\EntityFetcher;
use ORM\EntityManager;
use ORM\MockTrait;
use ORM\Test\Entity\Examples\Article;

class ExpectFetchTest extends MockeryTestCase
{
use MockTrait;

/** @var EntityManager|MockInterface */
protected $em;

protected function setUp()
{
$this->em = $this->emInitMock();
}

protected function tearDown()
{
\Mockery::close();
}

public function testReturnsFetcher()
{
$fetcher = $this->emExpectFetch(Article::class);

self::assertInstanceOf(EntityFetcher::class, $fetcher);

\Mockery::resetContainer();
}

public function testMocksFetch()
{
$fetcher = $this->emExpectFetch(Article::class);

self::assertSame($fetcher, $this->em->fetch(Article::class));
}

public function testReturnsNull()
{
$this->emExpectFetch(Article::class);

$fetcher = $this->em->fetch(Article::class);
$result = $fetcher->one();

self::assertNull($result);
}

public function testReturnsEntities()
{
$articles = [new Article(), new Article()];
$this->emExpectFetch(Article::class, $articles);

$fetcher = $this->em->fetch(Article::class);
$result = $fetcher->all();

self::assertSame($articles, $result);
}

public function testReturnsCount()
{
$articles = [new Article(), new Article()];
$this->emExpectFetch(Article::class, $articles);

$fetcher = $this->em->fetch(Article::class);
$result = $fetcher->count();

self::assertSame(2, $result);
}
}
86 changes: 86 additions & 0 deletions tests/MockTrait/ExpectInsertTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
<?php

namespace ORM\Test\MockTrait;

use Mockery\Adapter\Phpunit\MockeryTestCase;
use Mockery\MockInterface;
use ORM\EntityManager;
use ORM\MockTrait;
use ORM\Test\Entity\Examples\Article;
use ORM\Test\Entity\Examples\Category;

class ExpectInsertTest extends MockeryTestCase
{
use MockTrait;

/** @var EntityManager|MockInterface */
protected $em;

protected function setUp()
{
$this->em = $this->emInitMock();
}

protected function tearDown()
{
\Mockery::close();
}

public function testAllowsInsertOfSpecifiedClass()
{
$this->emExpectInsert(Article::class, ['id' => 42]);
$article = new Article();

$article->save();
}

public function testDoesNotAllowInsertsOfOtherClasses()
{
$this->emExpectInsert(Category::class);
$article = new Article();

self::expectException(\BadMethodCallException::class);
self::expectExceptionMessage('PDO::query() does not exist on this mock object');

try {
$article->save();
} catch (\Exception $e) {
\Mockery::resetContainer();
throw $e;
}
}

public function testSetsDefaultData()
{
$defaults = ['id' => 42, 'created' => date('c')];
$this->emExpectInsert(Article::class, $defaults);
$article = new Article();

$article->save();

self::assertSame($defaults, $article->getData());
}

public function testDoesNotOverwriteCurrentData()
{
$defaults = ['id' => 42, 'created' => date('c')];
$this->emExpectInsert(Article::class, $defaults);
$article = new Article();

$article->id = 1337;
$article->created = date('c', strtotime('-1 Hour'));
$article->save();

self::assertNotEquals($defaults, $article->getData());
}

public function testEmulatesAutoIncrementWithRandomValue()
{
$this->emExpectInsert(Article::class);
$article = new Article();

$article->save();

self::assertGreaterThan(0, $article->id);
}
}

0 comments on commit 4eb8903

Please sign in to comment.