I am working with Symfony2 and trying to write some PHPUnit test for one of my classes.
This is what I have done so far following Symfony2 testing Docs i created a folder under Tests directory with a file myClassTest.php ini it:
<?php
namespace Test\TestBundle\Tests\Template;
use Test\TestBundle\Dto\Template\myTemplate;
class myTemplateTest extends \PHPUnit_Framework_TestCase
{
public function testMyMethod()
{
$test = new myTemplate();
}
}
This is myTemplate:
<?php
namespace Test\TestBundle\Dto\Template;
use Test\TestBundle\Doctrine\DatabaseRepository;
use Test\TestBundle\Validation\ValidationClass;
class myTemplate
{
/**
* #var ValidationClass
*/
private $validate;
/**
* #var DatabaseRepository
*/
private $db;
/**
* #param ValidationClass $validation
* #param DatabaseRepository $databaseRepository
*/
public function __construct(
ValidationClass $validation,
DatabaseRepository $databaseRepository
) {
$this->validate = $validation;
$this->db = $databaseRepository;
}
}
Errors:
Argument 1 passed to Test\TestBundle\Dto\Template\myTemplate::__construct()
must be an instance of Test\TestBundle\Validation\ValidationClass, string given,
called in Dev/project/src/Test/TestBundle/Tests/Template/myTemplateTest.php
More Errors:
This error points to the injected service in the construct $validation
Dev/project/src/Test/TestBundle/Dto/Template/myTemplate.php:42
This error corresponds to the instantiate class of myTemplate class
Dev/project/src/Test/TestBundle/Tests/Dto/Template/myTemplateTest.php:15
I understand the errors well i think I do but i have no idea how to fix it and these errors are shown when i run phpunit test.
For testing with PHPUnit, you should use Mocks and Stubs for imitation logic in your depends classes.
Before initialize myTemplate in test case, you should create a mock of this objects, or create original objects.
$validationMock = $this->getMock('ValidationClass');
$dbRepositoryMock = $this->getMock('DatabaseRepository');
$myTemplate = new myTemplate($validationMock, $dbRepositoryMock);
And after you can use invocation system for control called methods.
For more information, please visit PHPUnit site:
https://phpunit.de/manual/current/en/test-doubles.html
Related
I want to mock a method foo in a class but leave method bar as it is:
<?php
class MyClass
{
protected $dep1;
protected $dep2;
protected $dep3;
protected $dep4;
/**
* Test constructor.
* #param $dep1
* #param $dep2
* #param $dep3
* #param $dep4
*/
public function __construct($dep1, $dep2, $dep3, $dep4)
{
$this->dep1 = $dep1;
$this->dep2 = $dep2;
$this->dep3 = $dep3;
$this->dep4 = $dep4;
parent::__construct();
}
public function foo()
{
return "foo";
}
public function bar()
{
return "bar";
}
}
However, MyClass is instantiated via a factory which retrieves the dependencies ($dep1, $dep2, ...) and inject them directly in to the constructor of MyClass.
So I want to use that Factory and instantiate a MyClass-object (otherwise the complex instantiation has to be coded in the test case also).
In short I want to know if there is another solution than:
class TestMyClass extends \PHPUnit\Framework\TestCase {
public function setUp()
{
// complicated way to retrieve $dep1 to $dep4
$mock = $this->getMockBuilder(MyClass::class)->setMethods(['foo'])->setConstructorArgs([$dep1, $dep2, $dep3, $dep4])->getMock();
$mock->expects($this->any())->method('foo')->willReturn(false);
}
}
Is it possible to somehow create proxy to an already created instance of MyClass, which just overrides the foo method? Is there another way (without runkit pecl extension) to mock an method of an existing/instantiated object?
I know PHPUnit has some Proxy-related methods, but I couldn't find any documentation / example of usage of them, so I am not sure if they even could be used to solve my problem.
So I stick PDO into my SLIM using this:
$container['dbh'] = function($container) {
$config = $container->get('settings')['pdo'];
$dsn = "{$config['engine']}:host={$config['host']};dbname={$config['database']};charset={$config['charset']}";
$username = $config['username'];
$password = $config['password'];
return new PDO($dsn, $username, $password, $config['options']);
};
However, every time I use $this->dbh->execute() (or some other PDO method), PhpStorm warns me method 'execute' not found in class
Realistically it doesn't make a difference but I'd like my PhpStorm to stop warning me about things that it doesn't need to.
This is mostly an answer adding to #rickdenhaan's comment.
I noticed that you're using $this, which implies you have a class somewhere.
You can typehint dynamic/fake properties in a class like so:
/**
* #property PDO $dbh
*/
class MyApp {
}
For more help, read the PHPDoc documentation, eg here.
In some situations, you might not be able to influence the original class being instantiated. In that case, you can have a stub file; basically a class used only for type hinting:
// Let's assume you cannot modify this class.
class App {}
// This is the stub class, we declare it as abstract to avoid instantiation by mistake.
// We make it extend `App` to get metadata from there.
abstract class AppStub extends App {
/** #var PDO */
public $dbh;
}
// ...later on in your code..
/** #var AppStub $this */
// tada!
$this->dbh->execute();
Controller classes approach
Your main app + routing:
$app = new \Slim\App();
$app->get('/', \YourController::class . ':home');
$app->get('/contact', \YourController::class . ':contact');
And your controller:
class YourController
{
protected $container;
// constructor receives container instance
public function __construct(ContainerInterface $container) {
$this->container = $container;
}
public function home($request, $response, $args) {
$this->getDb()->execute(); // no IDE errors anymore!
return $response;
}
public function contact($request, $response, $args) {
return $response;
}
/**
* #return PDO
*/
protected function getDb()
{
return $this->container->get('dbh');
}
}
If your class is in a namespace, you should use \PDO to indicate that you are referring to a class in the root namespace.
If PhpStorm has problems with identifying variable/object, so for example it has warning 'Method some_method not found in...' for example for code like:
$my_object->some_method();
then you can help PhpStorm to identify class by adding variable type definition in comment like this:
/**
* #var \Some\Class $my_object
*/
$my_object->some_method();
General example. Add this comment before using $my_object variable (for example at the beggining of method were you will use variable $my_object, or before first usage of this variable):
/**
* #var \Some\Class $my_object
*/
and now every time you will use this variable in method, PhpStorm will know that this variable is an instance of \Some\Class, so warnings will disappear in following lines, for every usage of this variable. For example warnings will not show for this kind of usage:
/**
* #var \Some\Class $my_object
*/
...
$my_object->some_method();
...
$my_object->some_method1();
...
$my_object->some_method2();
...
$my_object->some_method();
I have:
1. IntegrationTestCase extends TestCase
2. UnitTestCase extends TestCase
3. AcceptanceTestCase extends TestCase
In these I have quite a lot of non-static methods which are used in a lot of tests. All of my Test classes extend one of these 3 classes.
Now in a lot of Test classes I have a setUp method which preps the data and services needed and assigns them to class variables:
class SomeTestClass extends IntegrationTestCase
{
private $foo;
public function setUp()
{
parent::setUp();
$bar = $this->createBar(...);
$this->foo = new Foo($bar);
}
public function testA() { $this->foo...; }
public function testB() { $this->foo...; }
}
Problem is setUp is ran for each test defeating what I wanted to do and if what setUp method does takes a long time this is multiplied by the number of test methods.
Using public function __construct(...) { parent::__construct(..); ... } creates a problem because now lower level methods and classes from Laravel are not available.
For the next person running into this issue:
I had the problem that I wanted to migrate the database before running my tests but I didn't want the database to be migrated after each single test because the execution time would be way too high.
The solution for me was using a static property to check if the database was already migrated:
class SolutionTest extends TestCase
{
protected static $wasSetup = false;
protected function setUp()
{
parent::setUp();
if ( ! static::$wasSetup) {
$this->artisan('doctrine:schema:drop', [
'--force' => true
]);
$this->artisan('doctrine:schema:create');
static::$wasSetup = true;
}
}
}
Solution given by Saman Hosseini and similar didn't workout for me. Using the static property to flag getting reset for the next test class.
To overcome this I've wrote separate test class to test the test database connection & initialize the test database for once & made sure this runs before all the other tests
<?php
namespace Tests\Unit;
use Tests\TestCase;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Artisan;
/**
* #runTestsInSeparateProcesses
*/
class DatabaseConnectionTest extends TestCase
{
/**
* Test the database connection
*
* #return void
*/
public function testDatabaseConnection()
{
$pdo = DB::connection()->getPdo();
$this->assertNotNull($pdo);
}
/**
* Initialize the test database for once
*
* #return void
*/
public function testInititializeTestDatabase()
{
Artisan::call('migrate:fresh');
Artisan::call('db:seed');
}
}
I recommend using the template methods called setUpBeforeClass and tearDownAfterClass
The setUpBeforeClass() method is called before the first test is executed and the tearDownAfterClass() method is called after last test is executed.
We use these two methods to share settings with all the tests.
For example, it is wise to get the database connection once in the setUpBeforeClass() method then get connection multiple times in the setUp() method.
And likewise, it is wise to close the database connection only once in the tearDownAfterClass() method then close the database connection after every test in the tearDown() method.
I am not sure what issues you see for setUpBeforeClass is static except for the one mentioned by Mark Baker. I assume you do know what you are doing, though. Here is a sample of possible usage.
class BeforeAllTest extends PHPUnit_Framework_TestCase
{
private static $staticService;
private $service; // just to use $this is tests
public static function setUpBeforeClass() {
self::createService();
}
public static function createService(){
self::$staticService = 'some service';
}
/**
* just to use $this is tests
*/
public function setUp(){
$this->service = self::$staticService;
}
public function testService(){
$this->assertSame('some service', $this->service);
}
}
UPDATE: just somewhat similar approach you can see at https://phpunit.de/manual/current/en/database.html (search for 'Tip: Use your own Abstract Database TestCase'). I am sure you are already using it since you are doing intensive db testing. But nobody restricts this way for db-issues only.
UPDATE2: well, I guess you would have to use smth like self::createService instead of $this->createService (I've updated the code above).
Unable to inject Laravel's DB class into an abstract class located in another namespace folder.
Getting Error "Call to undefined method Illuminate\Support\Facades\DB::table()"
-Check:Working-
FrontController.php
<?php
use MyProject\MainPages\Front;
class indexController extends \BaseController {
/**
* #var Front
*/
private $Front;
/**
* #param ProductRepository $productRepo
*/
function __construct(Front $Front)
{
//Test
//$this->Front = $Front->goSetup();
}
}
-Check:Working-
Front.php
<?php namespace MyProject\MainPages;
use MyProject\MainPages\NavigationSkeleton;
class Front extends NavigationBluPrint {
/**
* Begins the process, Step 1
*/
protected function goSetup() {
// $this->DB->table() etc
}
}
-Not Working-
NavigationBluPrint.php
<?php namespace MyProject\MainPages;
use \DB;
abstract class NavigationBluPrint {
/**
* #var DB Laravel Database connection
*/
protected $dB;
public function __construct(DB $dB)
{
// SetDB so when extended it's already set for extended classes
$this->dB = $dB;
// Test below
$x = $this->dB->table('products')->get(); //Error: Call to undefined method Illuminate\Support\Facades\DB::table()
echo '<pre>';
print_r($x);
echo '<pre>';
}
}
If I need to do something with App:: to make this work, I dont understand how it's done. Thank you
Solution Found:In case someone else runs into the same problem.
In abstract class "NavigationBluPrint.php"
I replaced \DB; with=> use \Illuminate\Database\DatabaseManager as DB;
It seems to fix the problem, although I'm not sure whether its instantiating a new DB from start or using the same one. If former then it kind of defeats the purpose.
You're type hinting the facade for the DB class, not the DB class itself.
It's false the idea of using static method, via object and it is impossible.
you should create repository for your database operation then you can inject them greatly.
directions to create Eloquent repository or DB repository :
Here I found out a great article to do this.
How would i unit test an interface such as this following simple example:
interface My_App_My_Interface
{
/**
* #return int
*/
public function getInteger();
/**
* #return string
*/
public function getString();
}
also how would this be organised in my applications test directory:
tests > My > App > My > InterfaceTest ??
you don't test interfaces. you test implementation. interfaces should be checked (by human) if they provide all required functionality
As piotrek said, you will never test interfaces as they are just a contract, there is no code in there.
For example, with atoum testing framework you could write for a class that implements your interface.
namespace mageekguy\atoum\tests;
class TestMyInterfaceImplementation extends atoum\test{
public function test__construct(){
$object = new MyObject();
$this->object($object)->instanceof('MyInterface');
}
public function test_getInteger(){
$object = new MyObject();
$this->integer($object->getInteger);
}
}
As an interface just provides abstract methods, they just can't be instanciated so no test can be written.