Use mockery to unit test a class with dependencies - php

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);

Related

Phpunit mock only one method in tested class - using Mockery

I'm learning phpunit since week. I don't have idea how to mock only one method from tested class. (it's example only so I didn't write namespaces). Maybe you can help me
class SomeService
{
public function firstMethod()
{
return 'smth';
}
public function secondMethd()
{
return $this->firstMethod() . ' plus some text example';
}
}
and test:
class SomeServiceUnitTest extends TestCase
{
private $someService;
public function setUp()
{
parent::setUp();
$this->someService = new SomeService();
}
public function tearDown()
{
$this->someService = null;
parent::tearDown();
}
public function test_secondMethod()
{
$mock = Mockery::mock('App\Services\SomeService');
$mock->shouldReceive('firstMethod')->andReturn('rerg');
exit($this->walletService->secondMethd());
}
}
You can use a partial mocks, as example on your test class, you can do:
public function test_secondMethod()
{
$mock = Mockery::mock('App\Services\SomeService')->makePartial();
$mock->shouldReceive('firstMethod')->andReturn('rerg');
$this->assertEquals('rerg plus some text example', $mock->secondMethd());
}
Hope this help

Unit test returning injected object

I have a basic class which I inject into another class
AClass
{
protected $thing;
public function setThing($thing)
{
$this->thing = $thing;
}
public function getThing()
{
return $this->thing;
}
}
This class is the SUT.
AnotherClass
{
protected $aClass;
protected $someOtherClass;
__construct(AClass $aClass, SomeOtherClass $someOtherClass)
{
$this->aClass = $aClass;
$this->someOtherClass = $someOtherClass;
}
public function thatImTesting()
{
...
$thing = "logic from {$this->someOtherClass} and some other stuff";
return $this->aClass->setThing($thing);
}
}
So I want to test AnotherClass so I mock SomeOtherClass and inject it into the SUT. However, I create a new AClass and inject it in because I don't want to mock the functions (as that would make no sense).
$someOtherClassMock = m::mock(SomeOtherClass::class, [
// mocking the functions here
]);
$aClass = new AClass();
$anotherClass = new AnotherClass($aClass, $someOtherClassMock);
$this->assertEquals('Something', $anotherClass->getThing());
As $anotherClass object is returned and I need to call a function to check the data in the test, is this still a unit test?

Mocking a Propel Query in a function (Symfony2)

I am trying to write a unit test (using phpunit and mockery) for a class that uses a Propel query.
How do I mock the query $contact = ClientContactQuery::create()->findPK($id);
I am struggling to find any examples for this.
My class;
<?php
namespace MyBundle\Classes;
use MyBundle\Model\ClientContactQuery;
use MyBundle\Model\ClientContact;
class Contacts {
protected $_cache;
public function __construct($cache)
{
$this->_cache = $cache;
}
public function getContact($id)
{
$contact = ClientContactQuery::create()->findPK($id);
if (! $contact) {
throw new NotFoundHttpException('Client contact not found.');
}
return $contact;
}
}
My test case so far;
<?php
namespace MyBundle\Tests\Classes;
use Mockery as m;
use MyBundle\Classes\Contacts as c;
class ContactsTest extends \PHPUnit_Framework_TestCase
{
public function tearDown()
{
m::close();
}
public function testGetValidContact()
{
// Arrange
$cache = m::mock('cache');
// Act
$contact = new c($cache);
// am lost at this point :-(
// Assert
$this->assertInstanceOf('MyBundle\Classes\Contacts', $contact);
}
}
Static functions do not play nice with unit testing, and please do not create a private method and mock it.
I'd highly suggest creating a Query Factory. Not only this will give you ability to inject and unit test your code, but it will make life easier if you want to use XYZ orm instead Propel in the future.
#
<?php
namespace MyBundle\Classes;
use MyBundle\Model\ClientContactQuery;
use MyBundle\Model\ClientContact;
class Contacts {
protected $_cache;
/** #var QueryFactory */
private $queryFactory;
public function __construct( $cache, QueryFactory $queryFactory ) {
$this->_cache = $cache;
$this->queryFactory = $queryFactory;
}
public function getContact( $id ) {
$contact = $this->queryFactory->newClientContactQuery()->findPK($id);
if (! $contact) {
throw new NotFoundHttpException('Client contact not found.');
}
return $contact;
}
}
#
<?php
class QueryFactory {
const CLASS_NAME = __CLASS__;
public function newClientContactQuery() {
return ClientContactQuery::create();
}
public function newSomeOtherQuery() {
return SomeOtherQuery::create();
}
}
#
<?php
namespace MyBundle\Tests\Classes;
use Mockery as m;
use MyBundle\Classes\Contacts as c;
class ContactsTest extends \PHPUnit_Framework_TestCase {
public function tearDown() {
m::close();
}
public function testGetValidContact() {
$cache = m::mock( 'cache' );
$queryFactory = m::mock( QueryFactory::CLASS_NAME );
$clientContactQuery = m::mock( 'ClientContanctQuery' );
$contact = new c($cache, $queryFactory);
$queryFactory->shouldReceive('newClientContactQuery')->with()->once()->andReturn( $clientContactQuery );
$clientContactQuery->shouldReceive('findPK')->with('myTestInputId')->once->andReturn('something?');
$this->assertInstanceOf('MyBundle\Classes\Contacts', $contact);
}
}
You really can`t mock it, because you have "hard" dependency on it. So, to solve this problem, you should consider to move "hard" dependency on query from getContact method.
You can do it in three ways:
Create your private method, e. g. "getQueryFindByPk", and then mock it, on your Contacts class, to return what you need.
Pass query instance to constructor, but as I understand, you can have multiple query instances.
Create something like QueryFactory, Repository or QueryBuilder, which can return to you instance of query.
So, once again, problem is in having "hard" dependency on query.

Testing a private method in an abstract class extends the other one

I'm trying to test a private method in an abstract class.
I've got three abstract classes:
abstract class AbstractClass1 extends AbstractClass2
{
private function _privateFunction()
{
//method's body
}
}
abstract class AbstractClass2 extends AbstractClass3
{
public function __construct($param)
{
parent::__construct($param)
}
}
abstract class AbstractClass3
{
public function __construct($param = array())
{
//something
}
}
The test class:
class AbstractClass1Test extends PHPUnit_Framework_TestCase
{
public function test_privateFunction()
{
$stub = $this->getMockForAbstractClass("AbstractClass1");
$class = new ReflectionClass($stub);
$method = $class->getMethod("_privateFunction");
$method->setAccessible(true);
//some assertings with $method->invoke($stub)
}
}
The test failed, because of the error:
Missing argument 1 for AbstractClass2::__construct(), called in /usr/share/php/PHPUnit/Framework/MockObject/Generator.php on line 190 and defined
AbstractClass2.php
public function __construct($param)
AbstractClass1.php
$classMock = $this->getMockForAbstractClass("AbstractClass1");
Generator.php:190
if ($callOriginalConstructor &&
!interface_exists($originalClassName, $callAutoload)) {
if (count($arguments) == 0) {
<strong>$mockObject = new $mock['mockClassName'];</strong>
} else {
$mockClass = new ReflectionClass($mock['mockClassName']);
$mockObject = $mockClass->newInstanceArgs($arguments);
}
} else ...
What do I wrong? Or how can I test my private function in this situation?
You need to pass an argument to AbstractClass1's constructor. Pass constructor arguments in an array as the second argument to getMockForAbstractClass().
$stub = $this->getMockForAbstractClass("AbstractClass1", array('param'));
Seeing as you overrode the original constructor,
public function __construct($param = array()) //Allow null $param as it would default to array();
With a new one:
public function __construct($param) //Does not allow null $param.
You will require to define the $param when you initialize the object. That's probably your problem.
Objects in PHP are not like JavaScript, they cannot be called like associative arrays. Your object initialization should look like:
$mockObject = new ClassExtendingAbstractClass1Or2('parameter');
The new keyword cannot be used in front of a variable.

Magento - UnitTests - Mock Objects

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();
}
}

Categories