I have a test for testing data model:
<?php
namespace Tests\Model\SQL;
/**
* #group Model_Users
* #group Model
*/
class UsersTest extends TestCase {
/** #var Users */
private $model;
public function setUp() {
parent::setUp();
$this->connection->execute( file_get_contents(
__DIR__ . '/fixtures/countries.sql' ) );
$this->model = new \Users(
$this->connection
);
}
public function testUserExists() {
$exists = $this->model->userExists( 'johndoe' );
$this->assertTrue( $exists );
}
}
The Tests\Model\SQL\TestCase uses SQLite as a database to keep tests as fast as possible.
Anyway, as you may expect, SQLite is not the same database as Postgres, therefore there could be some specialites, which will fail on SQLite and pass on PostgreSQL and vice versa.
My question is:
How to effectively (when keeping DRY in mind) design the architecture to be able to run tests only with SQLite and then only with Postgres.
When I am saying DRY, I mean ideally case, when I write only one testcase for one model and the architecture of my tests will take care of that, so I will be able to run tests like this:
php tests/run.php --group=MockedDbWithSqlite # fast way, prefered, default
php tests/run.php --group=RealDb # slower way, but with more precise results
So judging from the comment the issue is rather independent of any concrete storage issue and more about how one can pass a custom parameter to PHPUnit.
Because, using this parameter, you'd switch out DB drivers.
There are a couple of options to do this but no officially supported "custom parameters" objects or something. Even so it would be nice if someone would add this maybe :)
My suggestion would be to use one of the following to options:
Env variables
Make the test/bootstrap code rely on $_ENV variables.
$dbDriverFactory->getInstanceFor(getenv('DB'));
and using one of the ways below to
export DB=postgress
phpunit
or
DB=postpress; phpunit;
or using phpunits xml config to set the env or whatever you like.
By doing so you could provide two xml configs phpunit-sqlite.xml & phpunit-pg.xml and run:
phpunit -c phpunit-sqlite.xml
It doesn't have to be env. Any of the methods listed in the doc section Setting PHP INI settings, Constants and Global Variables will work.
"Custom" cli parameters
Calling phpunit like this:
phpunit --colors --usePostgres
and adding something like this in test cases or bootstrap code:
if (in_array('--usePostgress', $_SERVER['argv'], true) {
$db = $this->setupPG();
} else {
$db = $this->setupSqlite();
}
I read this differently to edorian. I like to use inheritance for this kind of thing. The advantage is you can run both SQLite and Postgres tests in the same test run. The disadvantage is you have to add some code to each test file where you want to run with both databases. And you have to repeat this for each new database you want to test. Here is how it works:
class UsersTestBase extends TestCase {
//Unchanged
}
class UsersTestSQLite extends UsersTestBase{}
class UsersTestPostgres extends UsersTestBase {
function __construct(){
parent::_construct("postgres");
}
}
So, UsersTest is exactly the same, except the name has changed to append "Base".
You then have a derived UsersTestSQLite class. This does nothing. At this point we've not gained or lost anything.
Then we had UsersTestPostgres. This somehow tells the object to use postgres instead of sqlite. In this case I've assumed TestCase has a constructor that can take the database to use as an optional parameter (with the default being "sqlite"). Obviously there are lots of alternatives to that, but I hope you see the general idea.
Finally you need to add the suitable #group tags to the derived classes, to allow you control over when they are run.
Related
I've created a package that manipulates eloquent models, and I've created test cases for it.
The package is supposed to call save() or delete() for the models, at some point, which would attempt to hit the database.
Actual Database approach
As first attempt I tried this approach and also this. The point is that in the context of a package (even using orchestra/testbench) I'd need to configure a database and migrations. Since the package itself does not have any model (but I've created a dummy model for test purposes) I see this approach as possibly overkill. In any case, I'd still accept to setup a prepared sqlite db in memory, which I also tried but could not make it work (It was attempting to use forge connection instead sqlite and I could not make it access another connection. I may provide details on what I did for it).
Mocking approach
Another possible attempt (as per my limited understanding on this) is to partially mock the model. But after I mocked it, it would not know how to handle other calls such as fill(), for which I was willing the common behavior, but I received a method not found exception.
Method override approach
Given that these two possible attempts went bad, I defaulted to a third possible approach which worked for me, but I'm honestly not sure if this is the way to go about it.
In order to avoid test failure due to lack of database when calling save() or delete() methods, I overrided them (full source code here) :
class DummyContact extends Model
{
// ...
public function save(array $options = [])
{
$this->exists = true;
$this->wasRecentlyCreated = true;
}
public function delete()
{
$this->exists = false;
$this->wasRecentlyCreated = false;
}
}
This way, I'm able to test the following code (full source here):
public function unifyOnBase()
{
$mergeModel = $this->merge();
$this->modelA->fill($mergeModel->toArray());
$this->modelA->save();
$this->modelB->delete();
return $this->modelA;
}
So my question is, is this approach acceptable? (I think it is fair, I don't see exceptional pitfalls, but I suspect there exist more elegant approaches). In case there are suggested approaches, like Mocking or using an actual DB for running the tests, I'd like to know what adaptations should I do to my testing codebase for taking them.
Final but important note: I'm not willing to test models itself, I'm willing to test my code that uses (and thus depends on models).
Thanks to comment of Jonas Staudenmeir:
An integration test with an actual database is the most thorough way to test your code. IMO, you should at least have integration tests for the fundamental features. This is how I use an SQLite database for the tests of my package.
I could pair and get working the in-memory sqlite approach.
Removed override functions
Added Capsule test case setUp code as per this working project as example (Thanks for that)
In my case I also required to install the sqlite driver sudo apt-get install php7.2-sqlite
Tests now still run successfully, while the solution looks more elegant and cleans-up the function override workarounds which would easily break upon API upgrades of Eloquent Models. This would also enable easier access to testing relationship-dependent features of the package.
I think your first proposal, the actual database approach is the best - using the package orchestra/testbench.
Since you have no Eloquent models in your package, but your package modifies Eloquent models, I think you should create an Eloquent model only for testing purposes inside your test folder.
For example, put the DummyContact into tests/Models/DummyContact.php and put the migration file into tests/Database/Migration/DummyContactMigration.php.
Now all you need to do, is to setup a basic TestCase.php.
Make sure to explicitly call the migration file of your DummyContact model.
Here is an example:
<?php
namespace MyVendor\MyPackage\Tests;
use MyVendor\MyPackage\MyServiceProvider;
class TestCase extends \Orchestra\Testbench\TestCase
{
public function setUp(): void
{
parent::setUp();
$this->loadMigrationsFrom(__DIR__ . '/database/migrations');
$this->artisan('migrate', ['--database' => 'testbench'])->run();
}
/**
* add the package provider
*
* #param $app
* #return array
*/
protected function getPackageProviders($app)
{
return [MyServiceProvider::class];
}
/**
* Define environment setup.
*
* #param \Illuminate\Foundation\Application $app
* #return void
*/
protected function getEnvironmentSetUp($app)
{
// Setup default database to use sqlite :memory:
$app['config']->set('database.default', 'testbench');
$app['config']->set('database.connections.testbench', [
'driver' => 'sqlite',
'database' => ':memory:',
'prefix' => '',
]);
}
}
You may also want to read this blog post
I have a method with some logic in it and I'm not sure how to unit test it. Because it's a unit test for this specific method it should run without connecting to the database. I read about stubs and mockups but I can't find a way to apply them to this situation.
I would like to force the Client:GetClient to return the client object with the right properties so I can test each logic branch.
class ClientType {
function GetClientType($id) {
$objClient = Client::GetClient($id);
if ($objClient->Returning == 1) {
return 'returning';
}
else {
return 'normal';
}
}
This is the test I had in mind
class ResourceTest extends PHPUnit_Framework_TestCase {
function testGetClientType() {
$objClientType = new ClientType();
$this->assertTrue($objClientType->GetClientType(100), 'normal');
}
}
The problem is the dependency $objClient = Client::GetClient($id); The GetClient will pull a client from database but I need to replace this with a Stub so the unit tests work without real access to the database.
Conclusion
If you have code like the one presented: refactor it and use Dependency Injection.
If you have legacy code or just don't want to refactor try this solution: http://sebastian-bergmann.de/archives/885-Stubbing-Hard-Coded-Dependencies.html
With PHPUnit you can do
$class = $this->getMockClass(
'Client', /* name of class to mock */
array('getClient') /* list of methods to mock */
);
$class::staticExpects($this->any())
->method('getClient')
->will($this->returnValue('foo'));
In general, you want to avoid static methods though:
http://sebastian-bergmann.de/archives/883-Stubbing-and-Mocking-Static-Methods.html
https://kore-nordmann.de/blog/0103_static_considered_harmful.html
EDIT after update
PHPUnit can also stub hardcoded dependencies. See
Stubbing Hard-Coded Dependencies by Sebastian Bergmann
However, since you already noticed by now that it is a Pain the Behind to test statics and hardcoded dependencies, I suggest you remove the hardcoded dependency and static call with a real object that you inject into your ClientType instead.
Another option would be to use http://antecedent.github.io/patchwork (not affiliated with it), which
is a PHP library that makes it possible to redefine user-defined functions and methods
at runtime, loosely replicating the functionality runkit_function_redefine in pure PHP 5.3 code,
which, among other things, enables you to replace static and private methods with test doubles.
Try Mocking for such kind of a problem. I do not know PHP, but we do mocking in Java and C# in such a scenario.
EDIT
I'm sorry I didn't see that you were facing a problem with mocks. Well usually for Mocks you tell them what you expect from them and they respond to the same. LSV principle is being used in mocks. As for PHP, I'm sorry I have no idea on what tool to use for mocks and how you use them
I am quite new to the world of testing and I want to make sure I am on the right track.
I am trying to setup unit tests in a symfony2 project using phpunit.
PHPUnit is working and the simple default controller tests work fine. (Yet this is not about functional testing but unit testing my application.)
My project relies heavily on database interactions though, and as far as I understand from phpunit's documentation, I should set up a class based on \PHPUnit_Extensions_Database_TestCase, then create fixtures for my db and work from there.
Yet, symfony2 only offers a WebTestCase class which only extends from \PHPUnit_Framework_TestCase out of the box.
So am I right to assume that I should create my own DataBaseTestCase which mostly copies WebTestCase, only difference being that it extends from \PHPUnit_Extensions_Database_TestCase and implements all its abstract methods?
Or is there another "built-in" recommended workflow for symfony2 concerning database-centric tests?
As I want to make sure that my models store and retrieve the right data, I do not want to end up testing the specifics of doctrine by accident.
I have never used the PHPUnit_Extensions_Database_TestCase, mostly because for these two reasons:
It doesn't scale well. If you set up and tear down the database for every single test and you have a application which relies heavily on the database, you end up creating and dropping the same schema over and over again.
I like to have my fixtures not only within my tests but also within my development database and some fixtures are even needed for production (initial admin user or product categories or whatever). Having them inside an xml which can only be used for phpunit doesn't seem right to me.
My way in theory...
I use the doctrine/doctrine-fixtures-bundle for fixtures (no matter what purpose) and set up the whole database with all fixtures. I then execute all tests against this database and make sure to recreate the database if a test changed it.
The advantages are that I don't need to set up a database again if a test only reads but don't change anything. For changes I have to do drop it and create it again or make sure to revert the changes.
I use sqlite for testing because I can set up the database, then copy the sqlite file and replace it with a clean one to bring back the original database. That way I don't have to drop the database, create it and load all fixtures again for a clean database.
...and in code
I wrote an article about how I do database tests with symfony2 and phpunit.
Although it uses sqlite I think one can easily make the changes to use MySQL or Postgres or whatever.
Thinking further
Here are some other ideas which might work:
I once read about a test setup where before you use the database you start a transaction (within the setUp method) and then use the tearDown to rollback. That way you don't need to set up the database again and just need to initialize it once.
My setup described above has the drawback that the database is set up every time phpunit is executed, even if you only run some unit tests with no database interaction. I am experimenting with a setup where I use a global variable which indicates if the database was set up and then within the tests call a method whcih checks this variable and initializes the database if it didn't happened yet. That way only when a tests needs the database the setup would happen.
One problem with sqlite is that it doesn't work the same as MySQL in some rare cases. I had an issue once where something behaved different in MySQL and sqlite causing a test to fail when in with MySQL everything worked. I cannot remember what it was exactly.
tl;dr:
If and only if you want to go the whole functional test route, then I recommend looking up Sgoettschkes's answer.
If you want to unit test your application and have to test code that interacts with the database, either read on or jump directly to symfony2 docs (There is a more up-to-date version also talking about testing with fixtures for symfony5)
There were certain aspects in my original question that make it clear that my understanding of the differences between unit testing and functional tests was lacking. (As I have written I want to unit test the application, yet was also talking about Controller test at the same time; and those are functional test by defintion).
Unit testing only makes sense for services and not for repositories. And those services can use mocks of the entity manager. (I would even go as far as to say: If possible, write services that only expect entities to be passed into them. Then you only need to create mocks of those entities and your unit-tests of your business logic become very straightforward.)
My actual use case for my application was pretty well reflected on the symfony2 docs on how to test code that interacts with the database.
They provide this example for a service test:
Service class:
use Doctrine\Common\Persistence\ObjectManager;
class SalaryCalculator
{
private $entityManager;
public function __construct(ObjectManager $entityManager)
{
$this->entityManager = $entityManager;
}
public function calculateTotalSalary($id)
{
$employeeRepository = $this->entityManager
->getRepository('AppBundle:Employee');
$employee = $employeeRepository->find($id);
return $employee->getSalary() + $employee->getBonus();
}
}
Service test class:
namespace Tests\AppBundle\Salary;
use AppBundle\Salary\SalaryCalculator;
use AppBundle\Entity\Employee;
use Doctrine\ORM\EntityRepository;
use Doctrine\Common\Persistence\ObjectManager;
class SalaryCalculatorTest extends \PHPUnit_Framework_TestCase
{
public function testCalculateTotalSalary()
{
// First, mock the object to be used in the test
$employee = $this->getMock(Employee::class);
$employee->expects($this->once())
->method('getSalary')
->will($this->returnValue(1000));
$employee->expects($this->once())
->method('getBonus')
->will($this->returnValue(1100));
// Now, mock the repository so it returns the mock of the employee
$employeeRepository = $this
->getMockBuilder(EntityRepository::class)
->disableOriginalConstructor()
->getMock();
$employeeRepository->expects($this->once())
->method('find')
->will($this->returnValue($employee));
// Last, mock the EntityManager to return the mock of the repository
$entityManager = $this
->getMockBuilder(ObjectManager::class)
->disableOriginalConstructor()
->getMock();
$entityManager->expects($this->once())
->method('getRepository')
->will($this->returnValue($employeeRepository));
$salaryCalculator = new SalaryCalculator($entityManager);
$this->assertEquals(2100, $salaryCalculator->calculateTotalSalary(1));
}
}
No test database required for those kind of test, only mocking.
As it is important to test the business logic, not the persistence layer.
Only for functional tests does it make sense to have its own test database that one should build and tear down afterwards, and the big question should be:
When do functional test make sense?
I used to think that test all the things is the right answer; yet after working with lots of legacy software that in itself was barely test-driven developed I have become a bit more lazypragmatic and consider certain functionality as working until proven otherwise by a bug.
Assume I have an application that parses an XML, creates an object out of it, and stores those objects into a database. If the logic that stores the objects to the database is known to work (as in: the company requires the data and is, as of yet, not broke), and even if that logic is a big ugly pile of crap, there is no imminent need to test that. As all I need to make sure that my XML parser extracts the right data. I can infer from experience that the right data will be stored.
There are scenarios where functional test are quite important, i.e. if one were to write an online shop. There it would be business critical that bought items get stored into the database and here functional tests with the whole test database make absolute sense.
You can use this class:
<?php
namespace Project\Bundle\Tests;
require_once dirname(__DIR__).'/../../../app/AppKernel.php';
use Doctrine\ORM\Tools\SchemaTool;
abstract class TestCase extends \PHPUnit_Framework_TestCase
{
/**
* #var Symfony\Component\HttpKernel\AppKernel
*/
protected $kernel;
/**
* #var Doctrine\ORM\EntityManager
*/
protected $entityManager;
/**
* #var Symfony\Component\DependencyInjection\Container
*/
protected $container;
public function setUp()
{
// Boot the AppKernel in the test environment and with the debug.
$this->kernel = new \AppKernel('test', true);
$this->kernel->boot();
// Store the container and the entity manager in test case properties
$this->container = $this->kernel->getContainer();
$this->entityManager = $this->container->get('doctrine')->getEntityManager();
// Build the schema for sqlite
$this->generateSchema();
parent::setUp();
}
public function tearDown()
{
// Shutdown the kernel.
$this->kernel->shutdown();
parent::tearDown();
}
protected function generateSchema()
{
// Get the metadatas of the application to create the schema.
$metadatas = $this->getMetadatas();
if ( ! empty($metadatas)) {
// Create SchemaTool
$tool = new SchemaTool($this->entityManager);
$tool->createSchema($metadatas);
} else {
throw new Doctrine\DBAL\Schema\SchemaException('No Metadata Classes to process.');
}
}
/**
* Overwrite this method to get specific metadatas.
*
* #return Array
*/
protected function getMetadatas()
{
return $this->entityManager->getMetadataFactory()->getAllMetadata();
}
}
And then you can test your entity. Something like this (assuming you have a entity User)
//Entity Test
class EntityTest extends TestCase {
protected $user;
public function setUp()
{
parent::setUp();
$this->user = new User();
$this->user->setUsername('username');
$this->user->setPassword('p4ssw0rd');
$this->entityManager->persist($this->user);
$this->entityManager->flush();
}
public function testUser(){
$this->assertEquals($this->user->getUserName(), "username");
...
}
}
Hope this help.
Source: theodo.fr/blog/2011/09/symfony2-unit-database-tests
I would like to know where is the best place to set my db object with my model.
Should I hard coded it since my model should be designed for one project, so i set it inside my constructor or wherever i do initialization ?
or
Should I pass my db object to my constructor when instancing my object ?
What is the best way, i mean from experimented users, and efficient that'll give me more confort to use ?
Couple of things:
Most PHP projects that utilize a database connection represent that database using a Singleton pattern, if you aren't sure what this is, read up on it.
Typically I define my database connections in a configuration file which can easily be changed between environments (development, stage, production).
I'll then instantiate my database connection in a bootstrap file using the aforementioned Singleton pattern and configuration file.
My models will typically completely abstract the database/table data store, for each model I'll do something like this:
bootstrap.php
$config = load_config_data(ENVIRONMENT);
Db::setDefaultAdapter($config['database']);
Model/Table/User.php
class Table_User extends Db_Table
{
// Table name
protected $_name = 'user';
/* Do a bunch of database specific stuff */
}
Model/User.php
class User extends Model
{
public function updateUsername($userid, $username)
{
// Uses default adapter, Singleton pattern!
$table = Db::loadTable('user');
$table->update(
array('username'=>$username),
Db::quoteInto('userid = ?', $userid)
);
}
}
This is pretty much an introduction to the Model in the Zend Framework MVC, I would check it out for some ideas on how to organize your code (or save yourself some trouble and actually use the framework.)
For testability, you should pass it into the constructor rather than hard coding it. This helps you to write unit test because you can mock your DB object.
I would not hard code it, even if the code is never used for another project simply moving from a test database to a live database may require locating and changing the code in the model class. That would be far better placed in some kind of configuration file.
Personally, I would have the db object defined in whatever you use as a bootstrap - and then have the model(s) use that single object.
I am writing helper classes for a large project in PHP and I have written a class called Command. It is essentially an OOP wrapper around running system commands from PHP in a controlled way. It has methods like addOption() to add -a type options and addArgument() to add other arguments.
I will need to do a lot of scp'ing of files around so I will be using the Command class a lot. The calls to scp are very specific in that I need certain options used every time.
Does it make sense to extend this class and create a ScpCommand class? It seems to have an 'is a' relationship, but it is really just the same thing as the Command class with specific options every time. I don't really think I would need to override any methods with the exception of the constructor. I probably would add just class variables. It would really just be a convenience.
What makes the most sense here?
If it is just configuration, why not consider a factory which returns a Command after doing your boiler-plate configuration for you?
function getScpCommand($???)
{
$Command = new Command();
$Command->addOption(/* scp specific this */);
$Command->addArgument(/* scp specific that */);
/* etc */
$Command->addOption(/* handle your getScpCommand parameters here */)
return $Command;
}