I'm testing a Symfony command to send reminder Text messages. For this I have created a service for my text message interface and am mocking the container as well as the text messaging service:
The function under test
protected function textReminders()
{
$mailer = $this->getContainer()->get('mailer');
$em = $this->getContainer()->get( 'doctrine' )->getManager();
if ($this->getContainer()->get('kernel')->getEnvironment() == 'dev'){
$debug = true;
}else{
$debug = false;
}
$textMessage = $this->getContainer()->get('text_messaging.interface');
$textMessage->sendSMS( $target, $content, $debug);
}
Test
private function getMockContainer()
{
$container = $this->getMockBuilder('Symfony\Component\DependencyInjection\Container')
->disableOriginalConstructor()
->setMethods(array('get'))
->getMock();
return $container;
}
protected function setupMocks()
{
$mockText = $this->getMockBuilder('TextaHQ')
->disableOriginalConstructor()
->setMethods(array('sendSMS'))
->getMock();
$mockContainer = $this->getMockContainer();
$container = self::$kernel->getContainer();
$mockContainer->method('get')
->withConsecutive(['mailer'], ['doctrine'], ['kernel'], ['text_messaging.interface'])
->willReturnOnConsecutiveCalls(
$this->returnValue($container->get('mailer')),
$this->returnValue($container->get('doctrine')),
$this->returnValue(self::$kernel),
$this->returnValue($mockText))
;
$this->setMyMock([
'text' => $mockText,
'container' => $mockContainer
]);
}
public function testExecute()
{
$this->setupMocks();
self::bootKernel();
$application = new Application(self::$kernel);
$application->add(new ActionRemindCommand());
$command = $application->find( 'ahp:remind' );
$command->setContainer($this->getMyMock()['container']);
$commandTester = new CommandTester( $command );
$commandTester->execute( array(
'command' => $command->getName(),
'type' => 'text'
) );
$output = $commandTester->getDisplay();
$this->assertions();
}
protected function assertions()
{
$this->getMyMock()['text']
->expects( $this->once() )
->method( 'sendSMS' )
;
}
Updated test, all in one file
public function testExecute()
{
$insertSql = 'echo "'
. str_replace(
array('"' ,'`' ),
array('\\"' ,'\\`'),
$this->getPrepSql() )
. '" | mysql ahp_example_com';
exec($insertSql);
self::bootKernel();
$mockText = $this->getMockBuilder('TextaHQ')
->disableOriginalConstructor()
->setMethods(array('sendSMS'))
->getMock();
$mockContainer = $this->getMockBuilder('Symfony\Component\DependencyInjection\Container')
->disableOriginalConstructor()
->setMethods(array('get'))
->getMock();
$container = self::$kernel->getContainer();
$mockContainer->method('get')
->withConsecutive(['mailer'], ['doctrine'], ['kernel'], ['text_messaging.interface'])
->willReturnOnConsecutiveCalls(
$this->returnValue($container->get('mailer')),
$this->returnValue($container->get('doctrine')),
$this->returnValue(self::$kernel),
$this->returnValue($mockText))
;
$application = new Application(self::$kernel);
$application->add(new ActionRemindCommand());
$mailer = self::$kernel->getContainer()->get('swiftmailer.mailer');
$logger = new \Swift_Plugins_MessageLogger();
$mailer->registerPlugin( $logger );
$this->setMailCollector($logger);
$output = '';
for($i=1;$i<=$this->getRunNoTimes();$i++) {
$command = $application->find( 'ahp:remind' );
$command->setContainer($mockContainer);
$commandTester = new CommandTester( $command );
$commandTester->execute( array(
'command' => $command->getName(),
'type' => 'text'
) );
$output .= $commandTester->getDisplay();
}
$mockText
->expects( $this->once() )
->method( 'sendSMS' )
;
}
**PHPStorm Test call **
/usr/bin/php /home/jochen/projects/ahp/trunk/vendor/phpunit/phpunit/phpunit --configuration /home/jochen/projects/ahp/trunk/app/phpunit.xml.dist AgriHealth\AhpBundle\Tests\Command\ActionRemindCommandVet7DaysBeforeTest /home/jochen/projects/ahp/trunk/src/AgriHealth/AhpBundle/Tests/Command/RemindText/ActionRemindCommandVet7DaysBeforeTest.php --teamcity
Testing started at 10:25 AM ...
PHPUnit 5.5.4 by Sebastian Bergmann and contributors.
Expectation failed for method name is equal to <string:sendSMS> when invoked 1 time(s).
Method was expected to be called 1 times, actually called 0 times.
Time: 1.12 seconds, Memory: 18.00MB
FAILURES!
Tests: 1, Assertions: 1, Failures: 1.
Process finished with exit code 1
When I debug the test, I can see that $textMessage is a mock.
However at the end of the test in assertions(), I get an error:
Expectation failed for method name is equal to <string:sendsms> when invoked 1 time(s).
Method was expected to be called 1 times, actually called 0 times.
The debugger shows the mocked function as lower case: "sendsms", but renaming the function did not help.
here is a small illustartive case to clarify the points about I've mentioned in comments. Also it has a failing test for the situation when expectations are set after actuall call for the method was performed (which looks to be the case of your all-In-One-Method update test.
class MyClass
{
public function someMethod(){
}
}
class ExpectationsTest extends PHPUnit_Framework_TestCase
{
private $myClassMock;
public function setUp(){
$this->myClassMock = $this->getMock('MyClass');
}
public function testLocalMock(){
$localMock = $this->getMock('MyClass');
$localMock->expects($this->once())
->method('someMethod');
$localMock->someMethod();
}
public function testClassScopeMockInstance(){
$this->myClassMock->expects($this->once())
->method('someMethod');
$this->myClassMock->someMethod();
}
public function testWillFailBecauseExpectationWasSetAfterCall(){
$this->myClassMock->someMethod();
$this->myClassMock->expects($this->once())
->method('someMethod');
}
public function testCanUseHelperToCreateLocalMock(){
$mock = $this->createMyClassMock();
$mock->expects($this->once())
->method('someMethod');
$mock->someMethod();
}
private function createMyClassMock(){
return $this->getMock('MyClass');
}
public function testCanSetExpectationsInHelper(){
$this->setExpecatationsOnTestCaseScopeMock();
$this->myClassMock->someMethod();
}
private function setExpecatationsOnTestCaseScopeMock(){
$this->myClassMock->expects($this->once())
->method('someMethod');
}
}
btw I think I did not explore you code thoroughly enough for the first time. I think I might missassumed about how your setupMocks and getMyMock were intended to work. Sorry about that.
Related
I'm trying to write test code for monosilic code like below.
Q1. How to write test code which has access to DB?
Q2. How to refactor these code testable?
Q3. Is there any way to write test code with fewer change to production code?
I need your help!
Thanks!!
Example Production Code)
<?
class Sample_Model_Service_A
{
private $_result
private $_options
private $_someValue
public function __construct($params, $ids, $data) {
$this->_options = Sample_Model_Service_B::getOption($data);
}
private function setSomeValue() {
// some code shaping $_params to $someValue with $this->_options
$this->_someValue= $someValue;
}
// want to write test for this function
// changed this function's logic
private function setResult() {
// some code shaping $_someValue to $result
$this->_result = $result;
}
public function getter() {
retrn $this->_result;
}
}
?>
<?
class Sample_Model_Service_B
{
// get option from DB
public static function getOption($data) {
$dao = new Model_Dao_Option();
$option = $dao->getOption($data['id']);
return $option;
}
}
?>
My Test Code so far)
public function testsetResult()
{
// just make sure these variables are defined
$params = $ids = $data = [];
// try to make test for private function
$sample = new Sample_Model_Service_A($params, $ids, $data);
$reflection = new ReflectionClass($sample);
// get Method
$method = $reflection->getMethod('setresult');
$method->setAccessible(true);
// wondering how to get $result
$result = $method->invoke($sample);
// assert
$this->assertSame($result);
}
Mockery solved my issue.
Sample Code)
/**
* #dataProvider sampleProvider
*/
public function testsetResult($sampleData)
{
// mock Sample_Model_Service_B
$mockSample_Model_Service_B = Mockery::mock('alias:' . Sample_Model_Service_B::class);
$mockSample_Model_Service_B->shouldReceive('getOption')->andReturn($sampleData['option']);
$sample = new Sample_Model_Service_A($sampleData['params'], $sampleData['ids'], $sampleData['data']);
$sample->setResult();
$result = $sample->getter();
// assert
$this->assertSame($result, $sampleData['result']);
}
I am unable to set Argument while creating Unit test case in Symfony 3.4 console command application
My Console command
php bin\console identification-requests:process input.csv
My Console code
protected function execute(InputInterface $input, OutputInterface $output)
{
// Retrieve the argument value using getArgument()
$csv_name = $input->getArgument('file');
// Check file name
if ($csv_name == 'input.csv') {
// Get input file from filesystem
$csvData = array_map('str_getcsv', file($this->base_path.$csv_name));
$formatData = Helpers::formatInputData($csvData);
// Start session
$session = new Session();
$session->start();
foreach ($csvData as $key => $data) {
if (!empty($data[0])) {
$validation = Validator::getInformationData($data, $formatData[$data[1]]);
if (!empty($validation)) {
$output->writeln($validation);
} else {
$output->writeln('valid');
}
}
}
} else {
$output->writeln('Invalid file!');
}
}
I tried the following test code
$kernel = static::createKernel();
$kernel->boot();
$application = new Application($kernel);
$application->add(new DocumentCommand());
$command = $application->find('identification-requests:process')
->addArgument('file', InputArgument::REQUIRED, "input.csv");
$commandTester = new CommandTester($command);
$commandTester->execute(array(
'command' => $command->getName()
));
$output = $commandTester->getOutput();
$this->assertContains('valid',$output);
When I run unit test it showing the following error message
There was 1 error:
1) Tests\AppBundle\Command\DocumentCommandTest::testExecute
Symfony\Component\Console\Exception\LogicException: An argument with name "file" already exists.
I think you should put your input in the command tester and not in the command finder, in this case you are trying to create another parameter for that command, and that's why it's telling you that it is existing already.
try this
$command = $application->find('identification-requests:process');
$commandTester = new CommandTester($command);
$commandTester->execute(array(
'command' => $command->getName(),
'file' => 'input.csv'
));
This is the function I want to test on a manager (Genealogy Manager.php) When I run the phpunit command it gives the following error:
Call to a member function getSingleScalarResult() on null in /home/majri/Documents/projects/IadDirectoryApp/vendor/iad-holding/genealogy-bundle/Iad/Bundle/GenealogyBundle/Manager/GenealogyManager.php on line 77
/**
* #param Genealogy $genealogy
* #return mixed
*/
public function getCountManagerialLevelsByGenealogy(Genealogy $genealogy)
{
$qb = $this->getRepository()->createQueryBuilder('gen');
$qb->select('count(distinct(gen.level))')
->where($qb->expr()->lt('gen.right', $genealogy->getRight()))
->andWhere($qb->expr()->gt('gen.left', $genealogy->getLeft()))
;
return $qb->getQuery()->getSingleScalarResult();
}
** My function of Unit test **
public function testGetCountManagerialLevelsByGenealogy()
{
$em = $this->getMockBuilder(EntityManager::class)
->disableOriginalConstructor()
->getMock();
$repository = $this->getMockBuilder(EntityRepository::class)
->disableOriginalConstructor()
->getMock();
$em->expects($this->once())
->method('getRepository')
->with('IadGenealogyBundle:Genealogy')
->will($this->returnValue($repository));
$queryBuilder = $this->getMockBuilder(QueryBuilder::class)
->setMethods(['select','where','setParameter','getQuery'])
->disableOriginalConstructor()
->getMock();
$repository->expects($this->once())
->method('createQueryBuilder')
->with('gen')
->will($this->returnValue($queryBuilder));
$queryBuilder->expects($this->at(0))
->method('select')
->will($this->returnValue($queryBuilder));
$queryBuilder->expects($this->at(1))
->method('where')
->will($this->returnValue($queryBuilder));
$queryBuilder->expects($this->at(2))
->method('setParameter')
->will($this->returnValue($queryBuilder));
$getQuery = $this->getMockBuilder('\Doctrine\ORM\AbstractQuery')
->setMethods(array('setParameter', 'getSingleScalarResult'))
->disableOriginalConstructor()
->getMockForAbstractClass();
$getQuery->expects($this->once())
->method('setParameter')
->will($this->returnValue($queryBuilder));
$getQuery->expects($this->any())
->method('getSingleScalarResult')
->will($this->returnValue(3));
$queryBuilder->expects($this->at(3))
->method('getQuery')
->will($this->returnValue($getQuery));
$genealogyManager = new GenealogyManager($em);
$this->assertEquals(3, $genealogyManager->getCountManagerialLevelsByGenealogy($this->buildMockGenealogy(9)));
}
I am not sure if this the only error but your query needs to be setting the parameters correctly like so:
$qb->select('count(distinct(gen.level))')
->where($qb->expr()->lt('gen.right', ':getRight'))
->andWhere($qb->expr()->gt('gen.left', ':getLeft'))
->setParameters(array(
'genRight' => $genealogy->getRight(),
'genLeft' => $genealogy->getLeft()
))
;
Seems to me you forgot to mock the andWhere method (which thus returns null)
In my repositories, I have methods with too many arguments (for use in where) :
Example :
class ProchaineOperationRepository extends EntityRepository
{
public function getProchaineOperation(
$id = null, // Search by ID
\DateTime $dateMax = null, // Search by DateMax
\DateTime $dateMin = null, // Search by DateMin
$title = null // Search by title
)
In my controllers, I have differents action ... for get with ID, for get with ID and DateMin, for get ID and Title, ...
My method is too illegible because too many arguments ... and it would be difficult to create many methods because they are almost identical ...
What is the best practice ?
You have two main concerns in your question
You have too many arguments in your repository method which will be used in 'where' condition of the eventual query. You want to organize them in a better way
The repository method should be callable from the controller in a meaningful way because of possible complexity of arguments passed
I suggest you to write a Repository method like:
namespace AcmeBundle\Repository;
/**
* ProchaineOperationRepository
*
*/
class ProchaineOperationRepository extends \Doctrine\ORM\EntityRepository
{
public function search($filters, $sortBy = "id", $orderBy = "DESC")
{
$qb = $this->createQueryBuilder("po");
foreach ($filters as $key => $value){
$qb->andWhere("po.$key='$value'");
}
$qb->addOrderBy("po.$sortBy", $orderBy);
return $qb->getQuery()->getArrayResult();
}
}
The $filters variable here is an array which is supposed to hold the filters you are going to use in 'where' condition. $sortBy and $orderBy should also be useful to get the result in properly sequenced way
Now, you can call the repository method from your controller like:
class ProchaineOperationController extends Controller
{
/**
* #Route("/getById/{id}")
*/
public function getByIdAction($id)
{
$filters = ['id' => $id];
$result = $this->getDoctrine()->getRepository("AcmeBundle:ProchaineOperation")->search($filters);
//process $result
}
/**
* #Route("/getByTitle/{title}")
*/
public function getByTitleAction($title)
{
$filters = ['title' => $title];
$sortBy = 'title';
$result = $this->getDoctrine()->getRepository("AcmeBundle:ProchaineOperation")->search($filters, $sortBy);
//process $result
}
/**
* #Route("/getByIdAndDateMin/{id}/{dateMin}")
*/
public function getByIdAndDateMinAction($id, $dateMin)
{
$filters = ['id' => $id, 'dateMin' => $dateMin];
$sortBy = "dateMin";
$orderBy = "ASC";
$result = $this->getDoctrine()->getRepository("AcmeBundle:ProchaineOperation")->search($filters, $sortBy, $orderBy);
//process $result
}
}
Note that you are calling the same repository method for all controller actions with minor changes according to your parameters. Also note that $sortBy and $orderBy are optionally passed.
Hope it helps!
If your objective is only to query with an AND operator between each properties, the best way could be to use the method proposed by doctrine for that : findBy() cf : this part of the doc
for instance :
$results = $this
->getDoctrine()
->getRepository('AppBundle:ProchaineOperation')
->findBy(array('dateMax' => $myDate, 'title' => 'Hello world');
EDIT : after comment
Then use the same way as Doctrine do : Pass only an array with id, dateMax... as keys if these are set. This should be solve the method signature problem which gives you so much trouble. :)
I have a small problem which I think is quite simple to solve for experienced PHPUnit users.
I'm working with ZF2.
I'm working with a web service that returns plain text(CSV). I'd like to unit test the service that I've created.
I currently have a working configuration which is not the right way to do it I think.. I'm using mocks now when I'm unit testing my models and I have seen that PHPUnit has a special mock for web services, but that only supports WSDL.
Beneath you'll find my code and I hope someone can help me out with some explanation about the best practice for this situation.
The docs and the topics out here did not help me out (yet).
Thanks in advance!
The test itself:
public function testCanSearchSteeringWheels()
{
// Create the entry and fill it with the data that should be retrieved from the web service
$steeringWheelEntity = new SteeringWheelEntity();
$steeringWheelEntity->setId('170633')
->setName('Nice steering wheel one')
->setGrossPrice(100)
->setNetPrice(75);
// Setup the http client which whill make the final call to the web service
$httpClient = new Client();
$httpClient->setOptions(array(
'maxredirects' => 5,
'timeout' => 60,
))
->setAuth($this->config['supplier_name']['api']['username'], $this->config['supplier_name']['api']['password'])
;
$steeringWheelService = new SteeringWheelService($httpClient, new Request(), $this->config['supplier_name']);
// Search for a steering wheel by id code
$searchResult = $steeringWheelService->search('ID=5221552658987');
$this->assertEquals($steeringWheelEntity, $searchResult[0]);
}
The SteeringWheelEntity
namespace SupplierName\Entity;
class SteeringWheelEntity
{
// vars
// exchange array method
// getters methods
// setters methods
}
The SteeringWheelService
namespace SupplierName\Service;
use SupplierName\Entity\SteeringWheelEntity;
class SteeringWheelService extends AbstractWebService
{
/**
* search()
*
* #param string $param
* #return array
*/
public function search($param)
{
$this->appendUrl('ww0800?3,' . $param);
$response = $this->dispatch();
$parsedBody = $this->parse($response->getBody());
$entities = array();
foreach ($parsedBody as $data)
{
$steeringWheel = new SteeringWheelEntity();
// Fill SteeringWheelEntity with data
$entities[] = $steeringWheel;
}
return $entities;
}
}
The AbstractWebService
use \Zend\Http\Client;
use \Zend\Http\Request;
class AbstractWebService
{
private $httpClient;
private $request;
private $response;
protected $config;
private $url;
public function __construct(Client $httpClient, Request $request, Array $config)
{
$this->url = $config['api']['url'];
$this->httpClient = $httpClient;
$this->request = $request;
$this->config = $config;
}
protected function setUrl($url)
{
$this->url = $url;
return $this->url;
}
protected function appendUrl($string)
{
$this->url .= $string;
}
protected function getUrl()
{
return $this->url;
}
public function dispatch()
{
$this->request->setUri($this->getUrl());
$this->response = $this->httpClient->dispatch($this->request);
if (!$this->response->isSuccess()) {
throw new \Exception('HTTP error #' . $this->response->getStatusCode() . ' when connecting to ' . $this->getUrl() . '.');
}
return $this->response;
}
public function parse()
{
// Parse the content
}
}
Rather than using a mock for a web service. Could you just mock the \Zend\Http\Request and \Zend\Http\Client objects as they are doing the work for you? This way you have control over what the Zend objects return to you versus having to try to mock the web service.
That would be how I would go about testing the services.