I am running phpunit version 9.2 and I would like to know why my method is not covered in the phpunit coverage.
This is my class:
class Methods extends Template
{
const DISABLED_PAYMENT_METHODS_1 = 'free';
const DISABLED_PAYMENT_METHODS_2 = 'adyen_cc';
const DISABLED_PAYMENT_METHODS_3 = 'adyen_oneclick';
protected $paymentMethodList;
protected $storeManager;
protected $logger;
public function __construct(
Context $context,
PaymentMethodList $paymentMethodList,
StoreManagerInterface $storeManager,
LoggerInterface $logger,
array $data = []
) {
$this->paymentMethodList = $paymentMethodList;
$this->storeManager = $storeManager;
$this->logger = $logger;
parent::__construct($context, $data);
}
public function getPaymentMethods()
{
try {
$storeId = $this->storeManager->getStore()->getId();
$paymentList = $this->paymentMethodList->getActiveList($storeId);
$resultPayments = [];
foreach ($paymentList as $payment) {
if ($payment->getCode() !== self::DISABLED_PAYMENT_METHODS_1 &&
$payment->getCode() !== self::DISABLED_PAYMENT_METHODS_2 &&
$payment->getCode() !== self::DISABLED_PAYMENT_METHODS_3
) {
$resultPayments[] = $payment;
}
}
return $resultPayments;
} catch (Exception $e) {
$this->logger->error($e->getMessage());
return false;
}
}
}
and this is my test class:
class MethodsTest extends TestCase
{
private $model;
private function getSimpleMock($originalClassName)
{
return $this->getMockBuilder($originalClassName)
->disableOriginalConstructor()
->getMock();
}
public function setUp() : void
{
$context = $this->getSimpleMock(Context::class);
$paymentMethodList = $this->getSimpleMock(PaymentMethodList::class);
$storeManager = $this->getSimpleMock(StoreManagerInterface::class);
$logger = $this->getSimpleMock(LoggerInterface::class);
$this->model = new Methods(
$context,
$paymentMethodList,
$storeManager,
$logger,
[]
);
}
public function testGetPaymentMethods()
{
$stub = $this->createMock(Methods::class);
$stub->method('getPaymentMethods')
->willReturn([]);
try {
$stub->getPaymentMethods();
$this->fail("Expected exception!");
} catch (\Exception $error) {
$this->assertEquals("Expected exception!", $error->getMessage());
}
}
}
When I run the command to get the coverage. I am getting:
I am really curious why my test is not covered or at least the exception part ? Would you please share you ideas why ? and what can i do in order to fix this ? Right now I got a 29 % and I would like to get at least 60% coverage.
Thank you
On this line $stub = $this->createMock(Methods::class); you are creating a mock of the Methods class, so not actually testing the real class.
You will need to use the object you created in your setUp() method, and set up mock returns on the dependencies you passed in (perhaps converting some of them to be class properties).
You should test the real class, as example:
public function testGetPaymentMethods()
{
// define a payment
$paymentFreeCode = $this->createMock(Payment::class);
$paymentFreeCode->method('getcode')
->willReturn("free");
// define a payment
$payment = $this->createMock(Payment::class);
$payment->method('getcode')
->willReturn("invalid-code");
$paymentList = [
$paymentFreeCode,
$payment,
];
// define a store
$store = $this->createMock(Store::class);
$store->method('getId')
->willReturn("my-store-id");
// return store from the store manager
$this->storeManager->method('getStore')
->willReturn(myStore);
// return the payment list
$this->paymentMethodList->method('getActiveList')->with("my-store-id")
->willReturn($paymentList);
// call the real class instrumented with mocks
$paymentMethods = $this->model->getPaymentMethods();
$this->assertIsArray($paymentMethods);
$this->assertCount($paymentMethods, 1);
}
Related
Was testing an application and was repeatable getting the infamous new entity not configured to cascade persist error. I was surprised since I wasn't even creating new entities, and after digging into it, it appears to be relate to using different instances of the EntityManager object (I have confirmed that they are working with the same database, however) which I guess makes sense since each test will have a transaction applied. The only way I was able to get rid of the errors was to use the entityManager in the container instead of the autowired ones. While it works, it is a bit of a kludge and I would like to know the right way of doing this. Thank you
namespace App\Tests;
use ApiPlatform\Symfony\Bundle\Test\ApiTestCase;
class MyTest extends ApiTestCase
{
/**
* #dataProvider getData
*/
public function testWhichDoesNotWork(int $id, string $class)
{
$service = static::getContainer()->get(MyService::class);
$user = $service->getUser();
$randomEntity = $service->getRandomEntity($user->getTenant(), $class);
$randomEntity->setSomething('something');
$service->saveEntity($randomEntity);
}
/**
* #dataProvider getData
*/
public function testWhichWorks(int $id, string $class)
{
$service = static::getContainer()->get(MyService::class);
$em = static::getContainer()->get(EntityManagerInterface::class);
$user = $service->getUser();
$randomId = $service->getRandomEntityId($user->getTenant(), $class);
$randomEntity = $em->getRepository($class)->find($randomId);
$randomEntity->setSomething('something');
$em->persist($randomEntity);
$em->flush();
}
/**
* #dataProvider getData
*/
public function testAnotherWhichWorks(int $id, string $class)
{
$service = static::getContainer()->get(MyService::class);
$service->setNewEntityManager(static::getContainer()->get(EntityManagerInterface::class));
$user = $service->getUser();
$randomEntity = $service->getRandomEntity($user->getTenant(), $class);
$randomEntity->setSomething('something');
$service->saveEntity($randomEntity);
}
public function getData(): array
{
return [
[123, SomeClass::class]
];
}
}
namespace App\Test\Service;
final class MyService
{
public function __construct(private EntityManagerInterface $entityManager)
{}
public function setNewEntityManager(EntityManagerInterface $entityManager):self
{
$this->entityManager = $entityManager;
return $this;
}
public function getDatabase():string
{
return $this->entityManager->getConnection()->getDatabase();
}
public function getUser(int $id):User
{
return $this->entityManager->getRepository(User::class)->find($id);
}
public function getRandomId(Tenant $tenant, string $class):int
{
$meta = $this->entityManager->getClassMetadata($class);
$_sql = 'SELECT %s FROM public.%s WHERE tenant_id=? OFFSET floor(random() * (SELECT COUNT(*) FROM public.%s WHERE tenant_id=?)) LIMIT 1;';
$sql = sprintf($_sql, $meta->getSingleIdentifierFieldName(), $meta->getTableName(), $meta->getTableName());
return $this->entityManager->getConnection()->prepare($sql)->execute([$tenant->getId(), $tenant->getId()])->fetchOne();
}
public function getRandomEntity(Tenant $tenant, string $class):object
{
return $this->entityManager->getRepository($class)->find($this->getRandomId($tenant, $class));
}
public function saveEntity(object $entity):self
{
$this->entityManager->persist($entity);
$this->flush();
return $this;
}
}
services:
app.test.my.service:
alias: App\Test\Service\MyService
public: true
I'm trying to write a test class that tests when data is updated using the command in Symfony. But I don't know how to write a functional test. I need to know, which functions to write in the class UpdateTrackedContainerCommandTest, so that data was updated. In the documentation from Symfony I have not found any examples of it. Please, is there someone, who can give me some advice? I much appreciate any help.
Here is a command class in Symfony, that updates data through the command console:
#[AsCommand(
name: 'app:update-data',
description: 'Updating data',
hidden: false
)]
class UpdateDataCommand extends Command
{
private $repository;
private $apiService;
private $dbManager;
public function __construct(
Repository $repository,
ApiService $apiService,
DbManager $dbManager
) {
$this->repository = $repository;
$this->apiService = $apiService;
$this->dbManager = $dbManager;
parent::__construct();
}
protected function configure(): void
{
$this
->setHelp('This command allows you to update data...')
->setDescription('Updating data');
}
protected function execute(InputInterface $input, OutputInterface $output): int
{
$repositoryData = $this->repository->findAll();
$isUpdatedData = false;
foreach ($repositoryData as $dataRow) {
$apiData = $this->apiService->getApiData($dataRow->getReference())['result'] ?? null;
if ($apiData) {
foreach ($apiData['result'] as $key => $row) {
if ($row['id'] == $dataRow['id']) {
$dataRow[$key] = $row[$key];
$this->dbManager->persist($dataRow);
$isUpdatedData = true;
}
}
}
}
if ($isUpdatedData) {
$this->dbManager->flush();
}
return Command::SUCCESS;
}
}
Here is a test class in Symfony, that tests with PHP unit, that data was updated as expected:
class UpdateTrackedContainerCommandTest extends KernelTestCase
{
public function testExecute(): CommandTester
{
$kernel = self::bootKernel();
$application = new Application($kernel);
$command = $application->find('app:update-tracked-container');
$commandTester = new CommandTester($command);
$commandTester->execute([]);
$commandTester->assertCommandIsSuccessful();
return $commandTester;
}
}
So I created a test service set :
class FMaiAffaireServiceTest extends TestCase
{
/**
* #var MyService
*/
private $myService;
private $typeaffaireTable;
private $mockDriver;
private $mockConnection;
private $mockPlatform;
private $mockStatement;
private $adapter;
private $sql;
public function setUp()
{
$this->mockDriver = $this->getMock('Zend\Db\Adapter\Driver\DriverInterface');
$this->mockConnection = $this->getMock('Zend\Db\Adapter\Driver\ConnectionInterface');
$this->mockDriver->expects($this->any())->method('checkEnvironment')->will($this->returnValue(true));
$this->mockDriver->expects($this->any())->method('getConnection')->will($this->returnValue($this->mockConnection));
$this->mockPlatform = $this->getMock('Zend\Db\Adapter\Platform\PlatformInterface');
$this->mockStatement = $this->getMock('Zend\Db\Adapter\Driver\StatementInterface');
$this->mockDriver->expects($this->any())->method('createStatement')->will($this->returnValue($this->mockStatement));
$this->adapter = new Adapter($this->mockDriver, $this->mockPlatform);
$this->sql = new Sql($this->adapter);
$mockTableGateway = $this->getMock('Zend\Db\TableGateway\TableGateway', array(), array(), '', false);
$maiAffaireTable = $this->getMockBuilder('Maintenance\Model\BDD\FMaiAffaireTable')
->setMethods(array())
->setConstructorArgs(array($mockTableGateway, $this->adapter, $this->sql))
->getMock();
$stub = $this->returnValue(new ResultSet());
$maiAffaireTable->expects($this->any())->method('listAffaires')->will($stub);
$this->myService = new FMaiAffaireService(
$maiAffaireTable
);
}
public function testListAffaires()
{
$this->myService->listAffaires(1,10);
}
}
My service looks like this, it is a call to my Zend Db function :
class FMaiAffaireService
{
private $maiAffaireTable;
public function __construct(
$maiAffaireTable,
) {
$this->maiAffaireTable = $maiAffaireTable;
}
public function listAffaires($iOffset, $iLimit) {
$aResults = $this->maiAffaireTable->listAffaires($iOffset, $iLimit);
return $aResults->toArray();
}
}
And here is the sample of my Zend DB function :
class FMaiAffaireTable
{
protected $tableGateway;
protected $adapter;
protected $sql;
public function __construct(
TableGateway $tableGateway,
Adapter $adapter,
Sql $sql
) {
$this->tableGateway = $tableGateway;
$this->adapter = $adapter;
$this->sql = $sql;
}
public function listAffaires($iOffset, $iLimit)
{
try {
$resultSet = $this->tableGateway->select(
function (Select $select) use (
$iOffset,
$iLimit
) {
$select->offset($iOffset);
$select->limit($iLimit);
}
);
return $resultSet;
} catch (\Exception $e) {
throw new \Exception($e);
}
}
}
And there is a big problem at the execution of PHPUnit :
1) Directories\FMaiAffaireServiceTest::testListAffaires reset() expects parameter 1 to be array, null given
I don't call reset() ANYWHERE ! That's the problem ... I think it's a PDO function but ... I'm a bit lost.
Thanks.
The problem is here
$stub = $this->returnValue(new ResultSet());
$maiAffaireTable->expects($this->any())->method('listAffaires')->will($stub);
A non-initialized ResultSet will not have a datasource, running toArray() on it (as you do in your service) will first try and reset the datasource, which will be null.
Try
$resultSet = new ResultSet();
$resultSet->initialize(array());
$stub = $this->returnValue($resultSet);
How can I test a forward in a controller with PHPUnit?
I have two simple modules (A and B), module A call the module B using a forward.
here is a simple code that not work :
ModuleA
class ModuleAController extends AbstractRestfulController
{
protected $em;
public function setEntityManager(EntityManager $em)
{
$this->em = $em;
}
public function getEntityManager()
{
if (null === $this->em) {
$this->em
$this->getServiceLocator()->get('doctrine.entitymanager.orm_default');
}
return $this->em;
}
public function getList()
{
$data = array('message' => 'passed by module A');
$forward = $this->forward()->dispatch('ModuleB\Controller\ModuleB');
$data['Message'] = $forward->getVariable('Message');
return new JsonModel($data);
}
}
ModuleB
class ModuleBController extends AbstractRestfulController
{
public function setEntityManager(EntityManager $em)
{
$this->em = $em;
}
public function getEntityManager()
{
if (null === $this->em) {
$this->em = $this->getServiceLocator()->get('doctrine.entitymanager.orm_default');
}
}
public function getList()
{
$data = array('Message'=>'passed by module B');
return new JsonModel($data);
}
}
And this is a test code :
class ModuleAControllerTest extends AbstractHttpControllerTestCase
{
protected $controller;
protected $request;
protected $response;
protected $routeMatch;
protected $event;
protected function setUp()
{
$serviceManager = Bootstrap::getServiceManager();
$this->controller = new ModuleAController();
$this->request = new Request();
$this->routeMatch = new RouteMatch(array());
$this->event = new MvcEvent();
$config = $serviceManager->get('Config');
$routerConfig = isset($config['router']) ? $config['router'] : array();
$router = HttpRouter::factory($routerConfig);
$this->event->setRouter($router);
$this->event->setRouteMatch($this->routeMatch);
$this->controller->setEvent($this->event);
$this->controller->setServiceLocator($serviceManager);
}
public function testModuleAControllerCanBeAccessed()
{
$result = $this->controller->dispatch($this->request);
$response = $this->controller->getResponse();
$this->assertEquals(200, $response->getStatusCode());
$this->assertEquals(200, 1+99+100);
}
}
And this is the error message :
There was 1 error:
1) ModuleATest\Controller\ModuleAControllerTest::testModuleAControllerCanBeAccessed
Zend\ServiceManager\Exception\ServiceNotCreatedException: An exception was raised while creating "forward"; no instance returned
....
Caused by
Zend\ServiceManager\Exception\ServiceNotCreatedException: Zend\Mvc\Controller\Plugin\Service\ForwardFactory requires that the application service manager has been injected; none found
....
FAILURES!
Tests: 1, Assertions: 0, Errors: 1.
Is there any way to make this code work ??Any idea ??
Thank you.
I have not created mock for plugins yet. I don't know how set new plugin to controller. But mock will be like it.
PHPunit test file
public function testControllerWithMock()
{
/* Result from ModuleBController method getList() */
$forwardResult = new JsonModel(array('Message'=>'passed by module B'));
/* Create mock object for forward plugin */
$forwardPluginMock = $this->getMockBuilder('\Zend\Mvc\Controller\Plugin\Forward')
->disableOriginalConstructor()
->getMock();
$forwardPluginMock->expects($this->once())
->method('dispatch') /* Replace method dispatch in forward plugin */
->will($this->returnValue($forwardResult)); /* Dispatch method will return $forwardResult */
/* Need register new plugin (made mock object) */
$controller->setPluginManager(); /* ??? Set new plugin to controller */
I'm thinking how decide it.
Ok, try it.
$controller->getPluginManager()->injectController($forwardPluginMock);
I don't write PHPUnit tests for controllers. Because controllers must return a view and best solution using Selenium for testing view. I usually use PHPUnitSelenium tests for testing it.
I want to retrieve an instance of Zend_Session_Namespace within my models but I don't want them to have a concrete dependency on Zend's implementation (so I can mock it for it testing).
The session instance needs some configuration passed to it at call time. My other dependencies do not and can be configured during the bootstap process.
I have a very basic DI container, borrowed from Fabien Potencier:
class Lib_Container {
protected $services = array();
function __set($id, $service) {
$this->services[$id] = $service;
}
function __get($id) {
if (!isset($this->services[$id])) {
throw new ServiceNotRegisteredException(
"Service '$id' has not been registered"
);
}
if (is_callable($this->services[$id])) {
return $this->services[$id]($this);
}
return $this->services[$id];
}
}
I'm using this to wire up my dependencies:
$container = new Lib_Container;
$container->session = function($c) {
return new Zend_Session_Namespace($c->sessionName);
};
...
I'm using these dependencies within my base model (I don't want my model to know so much about my container configuration):
class Lib_Model {
protected $_container;
protected $_sessionName = 'default';
protected $_sessionInstance;
public function __construct($container) {
$this->_container = $container;
}
public function getDB() {
return $this->_container->database;
}
public function getRequest() {
return $this->_container->request;
}
public function getSession($ns = null) {
$ns = ($ns == null) ? $this->_sessionName : $ns;
if (!isset($this->_sessionInstance[$ns])) {
$this->_container->sessionName = $ns;
$this->_sessionInstance[$ns] = $this->_container->session;
}
return $this->_sessionInstance[$ns];
}
}
This enables my subclasses to retrieve a session instance reasonably conveniently:
class Model_User extends Lib_Model {
protected $_sessionName = 'user';
public function loggedIn() {
$session = $this->getSession();
return ($session && $session->loggedIn) ? true : false;
}
}
Or by passing the session namespace as an argument:
$session = $this->getSession('admin');
However, my Lib_Model::getSession() method is more complex than I would like, and knows too much about my DI container. Ideally want to obtain an instance of Zend_Session_Namespace by calling:
class Lib_Model {
protected $_sessionName = 'default';
protected $_sessionFactory;
...
public function __construct($container) {
$this->_sessionFactory = $container->session;
}
...
public function getSession($ns = null) {
$ns = ($ns == null) ? $this->_sessionName : $ns;
if (!isset($this->_sessionInstance[$ns])) {
$this->_sessionInstance[$ns] = $this->_sessionFactory($ns);
}
return $this->_sessionInstance[$ns];
}
}
I appreciate my DI container is checking if it's services are callable (e.g. anonymous functions) and executing them. If I remove this behaviour the auto-wiring element will crumble?
Any ideas how I can achieve $container->session('my_namespace') to return the equivalent of new Zend_Session_Namespace('my_namespace')?
Update: I thought I was on to something by changing the configuration of my container:
$container->session = function($c) {
$s = function($namespace) {
return new Zend_Session_Namespace($namespace);
};
return $s;
};
So that $container->session would return a function. Updating my Lib_Model class:
Lib_Model {
private $_sessionFactory;
...
public function __construct($container) {
...
$this->_sessionFactory = $container->session;
}
...
public function getSession($ns = null) {
$ns = ($ns == null) ? $this->_sessionName : $ns;
if (!isset($this->_sessionInstance[$ns]))
$this->_sessionInstance[$ns] = $this->_sessionFactory($ns);
return $this->_sessionInstance[$ns];
}
}
Unfortunately this gives me a 500 internal server error :(
I resolved the 500 internal server error by adjusting Lib_Model::getSession() slightly:
public function getSession($ns = null) {
$ns = ($ns == null) ? $this->_sessionName : $ns;
if (!isset($this->_sessionInstance[$ns])) {
$sessionFactory = $this->_session;
$this->_sessionInstance[$ns] = $sessionFactory($ns);
}
return $this->_sessionInstance[$ns];
}
I put together a simple script slowly building up it's complexity until it dawned on me I was calling an undefined method on Lib_Model, though no error message was displayed by PHP running under apache.
$f = function() {
return function($name) {
echo "Hello " . $name . PHP_EOL;
};
};
$hello = $f();
$hello("World");
unset($hello);
// second test
class Container {
protected $services = array();
function __set($id, $service) {
$this->services[$id] = $service;
}
function __get($id) {
if (!isset($this->services[$id])) {
throw new ServiceNotRegisteredException(
"Service '$id' has not been registered"
);
}
if (is_callable($this->services[$id])) {
return $this->services[$id]($this);
}
return $this->services[$id];
}
}
$c = new Container;
$c->h = function() {
return function($name) {
echo "Hello " . $name . PHP_EOL;
};
};
$hello = $c->h;
$hello("Bert");
// third test
class MyTest {
public $attr;
}
$test = new MyTest;
$test->attr = $c->h;
$test->attr("Ernie");
Test output:
$ php -f test.php
Hello World
Hello Bert
PHP Fatal error: Call to undefined method MyTest::attr() in /home/greg/test.php on line 53