I'm facing the following problem. I've wrote a function that create a connection object (AMQPConnection) given the required parameters. Now I want to write the corresponding unit test. I just don't know how to do it without having the RabbitMQ broker running. Here is the function in question:
public function getConnection($hostKey, array $params)
{
$connection = null;
try {
$connection = new AMQPConnection(
$params['host'],
$params['port'],
$params['username'],
$params['password'],
$params['vhost']
);
// set this server as default for next connection connectionAttempt
$this->setDefaultHostConfig($hostKey, $params);
return $connection;
} catch (\Exception $ex) {
if ($this->isAttemptExceeded()) {
return $connection;
} else {
// increment connection connectionAttempt
$this->setConnectionAttempt($this->getConnectionAttempt() + 1);
return $this->getConnection($hostKey, $params);
}
}
}
You usually don't test code like this as a Unittest since the result would more likely tell you that your server is installed correctly and not that your code is working.
Do you test if PDO returns a valid database connection?
It could make sense if you test your installation but not to test if the php c libs are working correctly.
You should add an ability to change the class being instantiated.
Add an ability to change the connector class via constructor or setter, with a default AMQPConnector class by default. Use it to create a connector object. Example
Create a mocked connector class for unit tests. Example
Use it setting the mock class via constructor or setter in tests. Example
You could also do assertions on what arguments are passed to the connector constructor.
Another way would be to use amqp interop so you are not coupled to any implementation. Much easier to tests as you deal with pure interfaces only.
#chozilla
#zerkms
So thanks to your hints, I've decided to use Closure in order to isolate the code portion connecting to the server.
Here is the closure:
$connectionFunction = function ($params) {
return new AMQPStreamConnection(
$params['host'],
$params['port'],
$params['username'],
$params['password'],
$params['vhost']
);
};
And here the modified getConnection() function
/**
* #param string $hostKey The array key of the host connection parameter set
* #param array $params The connection parameters set
* #return null|AMQPStreamConnection
*/
public function getConnection($hostKey, array $params)
{
$connection = null;
try {
$connection = call_user_func($connectionFunction, $params);
// set this server as default for next connection connectionAttempt
$this->setDefaultHostConfig($hostKey, $params);
return $connection;
} catch (\Exception $ex) {
if ($this->isAttemptExceeded()) {
return $connection;
} else {
// increment connection connectionAttempt
$this->setConnectionAttempt($this->getConnectionAttempt() + 1);
return $this->getConnection($hostKey, $params);
}
}
}
For the unit test I did the following:
$mockConnection = $this->getMockBuilder('PhpAmqpLib\Connection\AMQPStreamConnection')
->disableOriginalConstructor()
->getMock();
$connectionFunction = function ($params) use ($mockConnection) {
return $mockConnection;
};
Or this to have an Exception.
$connectionFunction = function ($params) {
throw new \Exception;
};
N.B.: I'm using AMQPStreamConnection in the getConnection() function since AMQPConnection is marked as deprecated in PhpAmqpLib
Related
I'm a little new to OO programming and am having trouble grasping why one mechanism works and another does not.
I've create a simple class that is to return a MySQL database handle. My attempt at returning the handle directly from a constructor fails. But succeeds from either a class method or from a class(?) method after an instance has been created. Here's the class definition and the sample script
<?php
class HMMDatabaseHandle {
private static $configfile = "config.json";
// uncomment for test 1
// public function __construct () {
// return self::get_handle_admin();
// }
public static function create() {
return self::get_handle_admin();
}
private static function get_handle_admin() {
$config = json_decode(file_get_contents(self::$configfile));
$dbhost = $config->database->dbhost;
$dbname = $config->database->dbname;
$dbuser = $config->database->dbuser;
$dbpass = $config->database->dbpass;
try {
return new PDO("mysql:host=$dbhost;dbname=$dbname", $dbuser, $dbpass);
}
catch(PDOException $e) {
echo $e->getMessage();
}
}
}
?>
And here is the test script I'm using:
<?php
require_once 'HMMDatabaseHandle.php';
// Test 1 - fails (uncomment constructor func) at call to prepare() with:
// PHP Fatal error: Call to undefined method HMMDatabaseHandle::prepare()
//$dbh = new HMMDatabaseHandle();
// Test 2 - works when class creates default constructor
// i.e. no explicit __construct() func
// Fetching data from executed query is fine
//$db = new HMMDatabaseHandle();
//$dbh = $db->create();
// Works using static class methods rather than instance
$dbh = HMMDatabaseHandle::create();
$sth = $dbh->prepare('select data_title,track_id from master');
$sth->execute();
while($row = $sth->fetch(PDO::FETCH_ASSOC)) {
...
}
My questions are:
Why can't I return the handle directly from a constructor when it seems so similar to calling the class method directly? Why does it matter whether the constructor calls the class method or my script calls it?
If I create an instance with PHP's default constructor, am I really calling a class method with $db->create()?
I seem to be missing some fundamental concept here. Thanks in advance!
You can't return the handle from the constructor in that context because that would violate the defined behavior of new. new SomeClass(); will only ever return an instance of the class, regardless of what other methods are called in the constructor.
__construct() is a void method. It is not intended to return anything1. That doesn't mean that the other code in it doesn't get executed, just that your return is disregarded in the context of creating a new object. This makes sense as the primary purpose for the constructor is to provide a means to pass dependencies to the object. Sometimes it is used to do additional initialization/setup of the object, but many people believe it should not do any work other than assigning the given arguments to the object's properties. Either way, there should be no need for it to return anything.
1 You can actually call the __construct() method explicitly after you create the object, and then it will behave like a normal method and your return will work.
$db = new HMMDatabaseHandle();
$dbh = $db->__construct();
var_dump($dbh); // PDO Object
This isn't a normal thing to do though, and I can't think of a scenario where it would be useful or desirable. I just wanted to point out that it is possible.
Why can't I return the handle directly from a constructor when it
seems so similar to calling the class method directly?
If you were able to do that, then you wouldn't have an instance of HMMDatabaseHandle; you'd have an instance of PDO. How would you access any other methods that HMMDatabaseHandle provides?
While I fully agree with #Don't Panic's answer, I need to also point out that you're mixing static and instance methods.
As a general rule of thumb, use the static keyword when you want to be able to call a method without instantiating the object first. If you want to actually create and use an object, then you can define you class like so and use $this-> (or $object-> if outside of the class) instead of :: to access instance properties and methods.
<?php
class HMMDatabaseHandle
{
private $configfile = "config.json";
public function __construct()
{
// You're not initializing anything in here, so this constructor is optional.
}
public function create()
{
return $this->get_handle_admin();
}
private function get_handle_admin()
{
$config = json_decode(file_get_contents($this->configfile));
$dbhost = $config->database->dbhost;
$dbname = $config->database->dbname;
$dbuser = $config->database->dbuser;
$dbpass = $config->database->dbpass;
try {
return new PDO("mysql:host=$dbhost;dbname=$dbname", $dbuser, $dbpass);
}
catch (PDOException $e) {
echo $e->getMessage();
}
}
}
To actually execute this, you need to now instantiate new class.
$dbManager = new HMMDatabaseHandle();
$handle = $dbManager->create();
Finally, there is a trick you can employ to make your constructor chainable. Simply wrap it in brackets.
$handle = (new HMMDatabaseHandle())->create();
My situation
I have a Symfony 4.2 project with the following structure:
src
Controller
Service
Twig Extensions
Templates
Tests
I use a database class, which internally creates a PDO connection. If i run my tests with PHPUnit, my database class has to switch from mysql to sqlite. Everything works fine here.
My problem
I can not keep the once created Database instance, when running just one test. Symfony seems to recreate it during the test: when inside a Twig template which uses a Twig extension. Because the database class uses
new \PDO('sqlite::memory:');
it loses created tables and therefore the test fails. I am aware of, that the Database instance (with PDO reference) gets reseted after each test, but in my situation i only have one test. How can i ensure, that it re-uses the Database instance?
Here the related code
InstanceExtension class provides the function title, which is used in a Twig template and has to access the database.
<?php
namespace App\TwigExtension;
use App\Service\Database;
use Twig\TwigFunction;
use Twig\Extension\AbstractExtension;
class InstanceExtension extends AbstractExtension
{
protected $db;
/**
* #param Database $db
*/
public function __construct(Database $db)
{
$this->db = $db;
}
/**
* Tries to return the title/name for a given ID.
*
* #param string $subject
* #param string|array $tables
* #param string $lang Default is 'de'
*
* #return string label or id, if no title/name was found
*/
public function title(string $subject, $tables, string $lang = 'de'): string
{
return $this->db->get_instance_title($subject, $tables, $lang);
}
}
In my services.yaml the Database class is set to public (which should enable reusing it, doesn't it?):
App\Service\Database:
public: true
Database class
Here is the part of the Database class which initializes the PDO connection. Production code, which uses MySQL instead, removed:
<?php
declare(strict_types=1);
namespace App\Service;
class Database
{
/**
* #param Config $app_config
*
* #throws \Exception
*/
public function __construct(Config $app_config)
{
global $sqlite_pdo;
try {
// non test
// ..
// test environment
} else {
$this->db_engine = 'sqlite';
// workaround to ensure we have the same PDO instance everytime we use the Database instance
// if we dont use it, in Twig extensions a new Database instance is created with a new SQLite
// database, which is empty.
if (null == $sqlite_pdo) {
$pdo = new \PDO('sqlite::memory:');
$sqlite_pdo = $pdo;
} else {
$pdo = $sqlite_pdo;
}
}
} catch (\PDOException $e) {
if (\strpos((string) $e->getMessage(), 'could not find driver') !== false) {
throw new \Exception(
'Could not create a PDO connection. Is the driver installed/enabled?'
);
}
if (\strpos((string) $e->getMessage(), 'unknown database') !== false) {
throw new \Exception(
'Could not create a PDO connection. Check that your database exists.'
);
}
// Don't leak credentials directly if we can.
throw new \Exception(
'Could not create a PDO connection. Please check your username and password.'
);
}
if ('mysql' == $this->db_engine) {
// ...
}
// default fetch mode is associative
$pdo->setAttribute(\PDO::ATTR_DEFAULT_FETCH_MODE, \PDO::FETCH_ASSOC);
// everything odd will be handled as exception
$pdo->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION);
$this->db = $pdo;
}
// ...
}
One test looks kinda like:
<?php
class SalesChannelControllerTest extends TestCase
{
public function setUp()
{
parent::setUp();
// init Database class, using SQLite
$this->init_db();
// further setup function calls
// SalesChannelController is a child of
// Symfony\Bundle\FrameworkBundle\Controller\AbstractController
$this->fixture = new SalesChannelController(
$this->db
// ...
);
}
/**
* Returns a ready-to-use instance of the database. Default adapter is SQLite.
*
* #return Database
*/
protected function init_db(): Database
{
// config parameter just contains DB credentials
$this->db = new Database($this->config);
}
public function test_introduction_action()
{
// preparations
/*
* run action
*
* which renders a Twig template, creates a Response and returns it.
*/
$response = $this->fixture->introduction_action($this->request, $this->session, 'sales_channel1');
/*
* check response
*/
$this->assertEquals(200, $response->getStatusCode());
}
}
My current workaround is to store the PDO instance in a global variable and re-use it, if required.
<?php
global $sqlite_pdo;
// ...
// inside Database class, when initializing the PDO connection
if (null == $sqlite_pdo) {
$pdo = new \PDO('sqlite::memory:');
$sqlite_pdo = $pdo;
} else {
$pdo = $sqlite_pdo;
}
If you need more info, please tell me. Thanks for your time and help in advance!
I am trying to write a function which its purpose is to test for a database connection. What it does is it instantiates a Database object, attempts to connect to the database and return the result.
The pseudo code looks like this
<?php
function testDatabaseLogin() {
// Get the database connection information from a file
// ...
// Database is a class which stores the connection information passed,
// When its connect() method is invoked, it tries to connect. nothing fancy
$conn = new Database($host, $port, $username, $password);
return $conn->connect(); // TRUE on success, FALSE otherwise
}
?>
This code snippet is a simplified version of my situation, but it should be suffice to illustrate my question.
I would like to know how I could use PHPUnit to mock the Database class in this example. What I want is I can
Assert that the arguments when instantiating Database from this function is correct
Stub the Database to control connect() to return TRUE or FALSE, so that I don't need to actually attempt to connect to a real database
I have searched around but I found that it seems like PHPUnit Mock object does not work in this way. Any help is appreciate. And at the bottom line, it is acceptable to change the function implementation if it helps to make the testing easier.
Thank you.
There's a trick i've used sometimes
<?php
class MyClass
{
public function databaseLogin() {
$conn = $this->createDatabase();
return $conn->connect(); // TRUE on success, FALSE otherwise
}
protected function createDatabase() {
new Database($host, $port, $username, $password);
}
}
Then, create a "Class under test" that extends your class:
class MyClassUnderTest extends MyClass
{
public function __construct($databaseMock)
{
$this->databaseMock = $databaseMock;
parent::__construct();
}
}
And then, in the test:
class MySuperTest
{
public function testSomethingWitDatabase()
{
$databaseMock = $this->getMockBuilder(DataBase::class)
->disableOriginalConstructor()
->getMock();
$classUnderTest = new MyClassUnderTest($databaseMock);
$this->assertTrue($classunderTest->databaseLogin());
}
}
So you are instantiating your Database object into a method that is overwritten by a class just created for testing.
I have a class called DB (class.pdo.php) that does all the handling on mysql queries using PDO and another class called user that I use to manage a login system.
My question relates to always having to instantiate the $db in every public function of users so I can use DB. Is this efficient? Shouldn't I be instantiating DB inside the __construct() of users?
This is my code
require_once("../../class.pdo.php");
class user {
private $db = null;
public function __construct(){
/* Empty? */
}
public function find_by_email($email){
$db = new db();
$db->query('SELECT * FROM users WHERE email = :email LIMIT 1');
$db->bind(':email',$email);
$result = $db->single();
return $result;
}
public function create($email,$password,$first_name,$last_name){
$db = new db();
$db->query("INSERT INTO users(email,password,first_name,last_name,created_at) VALUES (:email,:password,:first_name,:last_name,NOW())");
$db->bind(':email',$email);
$db->bind(':password',$password);
$db->bind(':first_name',$first_name);
$db->bind(':last_name',$last_name);
$result = $db->execute();
return $db->lastInsertId();
}
[more similar functions ommited]
Well, despite of some comments suggesting the use of the Singleton pattern, I totaly disagree in using it for this purpose.
Your application will not always use a single connection to just one database.
Let me show you how I'd do this:
class DbConnector {
private $dbh;
private $dsn;
public function __construct($dsn) {
$this->dsn = $dsn;
}
private function connect() {
if($this->dbh === null) {
$this->dbh = new PDO($this->dsn);
}
}
public function disconnect {
if($this->dbh !== null) {
$this->dbh = null;
}
}
public function query($sql) {
$this->connect();
//... do the rest
}
public function fetchAll($sql) {
$this->connect();
//... do the rest
}
public function insert($table, $values) {
$this->connect();
//... do the rest
}
public function update($table, $values, $cond) {
$this->connect();
//... do the rest
}
public function delete($table, $cond) {
$this->connect();
//... do the rest
}
}
class User {
private $dbConn;
public function __construct(DbConnector $dbConn) {
$this->dbConn = $dbConn;
}
public function create($email,$password,$first_name,$last_name){
$this->dbConn->query("INSERT INTO users(email,password,first_name,last_name,created_at VALUES (:email,:password,:first_name,:last_name,NOW())");
$this->dbConn->bind(':email',$email);
$this->dbConn->bind(':password',$email);
$this->dbConn->bind(':first_name',$email);
$this->dbConn->bind(':last_name',$email);
$this->dbConn->execute();
return $this->dbConn->lastInsertId();
}
// ...
}
Results:
No singleton used = testable.
Connection to the database is just openned when needed
Your connection is persistent. If you open and close connections in every method, you loose the capability of creating transactions.
What about using the Singleton pattern to create one object for the connection and use it everytime you need it, instead of creating new objects all the time?
I would do something similar with lazy loading: don't initiate in the constructor unless you're sure you actually need the connection every time an object is created but absolutly don't create a new object on each method call. Instead, save the resulting object into an object var which is checked on each method call and initiates the connection if missing.
class user {
protected $_db = null;
private function _init_db() { $this->_db = new XXX; }
public function create( $x, $y, $z ) {
if ( ! $this->_db ) $this->_init_db();
# use $this->_db ..
}
public function find_by_email( $x, $y, $z ) {
if ( ! $this->_db ) $this->_init_db();
# etc
}
}
This has the advantages of avoiding global static state (singletons..) and only creates the connection / object at the very last moment so you're sure you actually need it and it's not just a useless connection.
Speaking of efficiency, the main problem with your code is that it establishes new connection for the every method called. This one is indeed inefficient to the point of killing your database server. And it's incomparable to the other problem you have.
So, in general, you can have whatever way you want - either get somehow an instance of db class in the every function or use a class variable - but either way have to use single PDO instance throughout whole application.
Also I find your functions quite inefficient from the amount of code point of view, and would have optimized them this way
public function create($email,$password,$first_name,$last_name){
$sql = "INSERT INTO users(email,password,first_name,last_name,created_at) VALUES (?,?,?,?,NOW())";
$this->db->query($sql);
$result = $db->execute(func_get_args());
return $db->lastInsertId();
}
From a object point of view, I'd leave database instantiating within the methods, rather than an entire class.
Each method should only see the variables and data it needs, in order to perform its function.
For instance, a createUser() method would need to see variables or properties such as $username, $usergroupId, as well as $database etc.
However, you may have a function which is called randomPassword(), which generates a random password from numbers and letter.
This randomPassword() function doesn't need the database object and therefore, an already initialised database connection in the object scope would be wasteful.
It would be better only to create the new database object in methods that required it.
In addition, in my application, I don't create a new database connection each time I used new database. Instead, I've opted for a singleton PDO database object which keeps the connection active.
I can then just call the database object statically to retrieve an existing connection. Therefore, if, in the process of running my application I need to have 20 database objects, my application then only returns the same object, and the same connection.
PHP PDO Singleton Class:
<?php
require_once('app/config.php'); // Require constants HOST, DATABASE, USER, PASSWORD
/*
dbConnection class.
Manages connections to and operations on the database. Call dbConnection::getInstance() to return an instance.
Prepare your statements by calling prepareQuery() on the object
Attribute list:
$instance:
> Static self instance to manage database resource
$connection:
> Holds connection resource
$sth:
> Statement handler variable. Handles SQL statements.
_______________________________________________________________________________________________________________
Method list:
getInstance():
> Creates or returns existing connection to the database
prepareQuery():
> Prepares the $sth variable for execution.
bindParameter():
> Binds parameters to the $sth variable.
numRows($query):
> Returns the number of returned from a query
runQuery():
> Executes the current statement on the database
fetchRow():
> Executes the current statement then returns an associative array
fetchObj($className, $parameters = NULL):
> Executes the current statement and returns an object of your specification. Also takes additional parameters to pass to the object's constructor.
*/
class dbConnection
{
private static $instance = NULL;
private $connection;
private $sth;
function __construct()
{
$this->connection = new PDO('mysql:host=' . HOST . ';dbname=' . DATABASE, USER, PASSWORD);
}
function getInstance()
{
if (self::$instance == NULL)
self::$instance = new dbConnection();
return self::$instance;
}
function prepareQuery($query)
{
$this->sth = $this->connection->prepare($query);
}
function bindParameter($number, $value)
{
$this->sth->bindParam($number, $value);
}
function numRows()
{
try
{
$this->sth->execute();
$count = $this->sth->rowCount();
return $count;
}
catch(PDOException $e)
{
echo __LINE__.$e->getMessage();
}
}
function runQuery()
{
try
{
$this->sth->execute() or print_r($connection->errorInfo());
}
catch(PDOException $e)
{
echo __LINE__.$e->getMessage();
}
}
function fetchRow()
{
try
{
$this->sth->setFetchMode(PDO::FETCH_ASSOC);
$this->sth->execute();
return $this->sth;
}
catch(PDOException $e)
{
echo __LINE__.$e->getMessage();
}
}
function fetchObj($className, $parameters = NULL)
{
try
{
$this->sth->setFetchMode(PDO::FETCH_CLASS, $className, $parameters);
$this->sth->execute();
return $this->sth;
}
catch(PDOException $e)
{
echo __LINE__.$e->getMessage();
}
}
}
?>
How to test singleton?
Take an object at a time, which is closed when you are finished with the object at the end.
I think you are misapplying the Singleton pattern here.
Nevertheless, testing Singletons is possible. Quoting Testing Code that uses Singletons
PHPUnit has a backup/restore mechanism for static attributes of classes.
This is yet another feature of PHPUnit that makes the testing of code that uses global state (which includes, but is not limited to, global and superglobal variables as well as static attributes of classes) easier.
Also see http://www.phpunit.de/manual/current/en/fixtures.html#fixtures.global-state
The #backupStaticAttributes annotation that is discussed in the section called “#backupStaticAttributes” can be used to control the backup and restore operations for static attributes. Alternatively, you can provide a blacklist of static attributes that are to be excluded from the backup and restore operations like this
So if you wanted to disable the backup, you'd do
class MyPdoTest extends PHPUnit_Framework_TestCase
{
protected $backupStaticAttributesBlacklist = array(
'dbConnection' => array('instance')
);
// more test code
}
Also have a look at the chapter on Database Testing
Singletons are bad news, as I have explained here.
More specifically to your question, Singletons are extremely difficult to unit test. Which is one of the reasons why they are bad news.
Use dependency-injection instead.
EDIT TO ADD:
I suppose you could write a set of unit tests where each unit test is in its own test suite and all the test suites are set to run in a separate process. That should isolate each unit test from the others.
The performance would be horrible though, and you're really better off just using a DI approach instead.
(I'm assuming you're using phpunit for your unit tests here)