PHPUnit, Interfaces and Namespaces (Symfony2) - php

I'm currently working on an open source bundle for Symfony2, and really want it to be the dogs nadgers in terms of unit test coverage and general reliability, however I've run into a snag due to my lack of PHPUnit knowledge (or a complex scenario, who knows)..
At present, I have a Mailer class, for handling individual mail scenarios. It looks a bit like this:
<?php
use Symfony\Bundle\FrameworkBundle\Templating\EngineInterface;
use Symfony\Component\Routing\RouterInterface;
class Mailer
{
protected $mailer;
protected $router;
protected $templating;
protected $parameters;
public function __construct($mailer, RouterInterface $router, EngineInterface $templating, array $parameters)
{
$this->mailer = $mailer;
$this->router = $router;
$this->templating = $templating;
$this->parameters = $parameters;
}
}
Simple enough, got some Symfony2 interface gubbins in there to handle different routing and templating systems, happy happy joy joy.
Here's the initial test I tried setting up for the above:
<?php
use My\Bundle\Mailer\Mailer
class MailerTest extends \PHPUnit_Framework_TestCase
{
public function testConstructMailer
{
$systemMailer = $this->getSystemMailer();
$router = $this->getRouter();
$templatingEngine = $this->getTemplatingEngine();
$mailer = new Mailer($systemMailer, $router, $templatingEngine, array());
}
protected function getSystemMailer()
{
$this->getMock('SystemMailer', array('send');
}
protected function getRouter()
{
$this->getMock('RouterInterface', array('generate');
}
protected function getTemplatingEngine()
{
$this->getMock('RouterInterface', array('render');
}
}
The problem here is that my mock objects do not implement Symfony\Bundle\FrameworkBundle\Templating\EngineInterface and Symfony\Component\Routing\RouterInterface, so I can't use any mock objects that I create myself. One method I have tried is creating an abstract class which implements the correct interface on the test page, however the getMockForAbstractClass fails, stating it can't find the class...

When mocking you need to use the full qualified class path as the mock functionality is not taking the namespace of the calling code or any "use" statements into consideration.
Try
->getMock('\\Symfony\\Component\\Routing\\RouterInterface');
and leave out the second parameter. Usually specifying the methods does a lot more worse than good.
Only if you want all the other methods to work like before than you should need the second parameter.
Example
<?php
namespace bar;
class MyClass {}
namespace foo;
use \bar\MyClass;
class MockingTest extends \PHPUnit_Framework_TestCase {
public function testMock() {
var_dump($this->getMock('MyClass') instanceOf MyClass);
var_dump($this->getMock('\\bar\\MyClass') instanceOf MyClass);
}
}
Produces:
/phpunit.sh --debug fiddleTestThree.php
PHPUnit #package_version# by Sebastian Bergmann.
Starting test 'foo\MockingTest::testMock'.
.bool(false)
bool(true)

Related

How to test an abstract class?

I am confused in the way how to start testing an abstract class.
Should I test every method?
What should I test?
Should I not test abstract classes?
An example:
abstract class Command
{
private $params;
public function with(array $params = [])
{
$this->params = $params;
}
public function getParams()
{
return $this->params;
}
abstract public function run();
}
Should I test it like:
/** #test */
public function is_an_abstract_class()
{
$command = $this->getReflectionClass();
$this->assertTrue($command->isAbstract());
}
/** #test */
public function has_an_run_method()
{
$command = $this->getReflectionClass();
$method = $this->getReflectionMethod('run');
$this->assertTrue($command->hasMethod('run'));
$this->assertTrue($method->isAbstract());
$this->assertTrue($method->isPublic());
$this->assertEquals(0, $method->getNumberOfParameters());
}
Should I not test abstract classes?
Under most circumstances, this would be my choice.
Reason #1: the fact that some class inherits from an abstract class is an implementation detail, not a behavior. We don't want to couple our tests to implementation details.
Reason #2: I would expect the code in the abstract class to be covered by the tests that cover its descendants.
If you design were emerging "test first", then you already have coverage of this code, because the abstract class would be something that you would introduce to your design via refactoring a class that was already under test.

A method Bar in class Foo logs a message with debug method. How to write testcode for method Bar

My class Foo has a method called Bar which is when called logs a debug message. Class Foo gets \Psr\Log\LoggerInterface $logger in its __contruct method. I'v created a testBar method in my FooTest Class but debug method in my Bar method is giving following error
PHP Fatal error: Class Mock_LoggerInterface_a49cf619 contains 8 abstract >methods and must therefore be declared abstract or implement the remaining >methods (Psr\Log\LoggerInterface::emergency, Psr\Log>\LoggerInterface::alert, Psr\Log\LoggerInterface::critical, ...) in /var/www/html/myproject/vendor/phpunit/phpunit-mock-objects>/src/Generator.php(264) : eval()'d code on line 1
My class code is given below
use Psr\Log\LoggerInterface;
class Foo {
private $logger;
private $myclassObject;
public function __construct(LoggerInterface $logger)
{
$this->logger = $logger;
}
public function Bar ()
{
// some code
$logger->debug ('debug message')
}
}
My test class given below
use PHPUnit\Framework\TestCase;
class FooTest extends TestCase
{
private $logger;
public function setUp()
{
$this->logger = $this->getMockBuilder('\Psr\Log\LoggerInterface')
->setMethods(null)
->getMock();
$this->logger->expects($this->any())
->method('debug')
->willReturn('Message Logged');
}
$this->myclassObject = $this->getMockBuilder('MyVendor\MyModule\Model\Foo')
->setMethods(['__construct'])
->setConstructorArgs(['$logger'])
->disableOriginalConstructor()
->getMock();
public function testBar()
{
$this->assertEquals($expected_result,$this->myclassObject->Bar());
}
}
I'm expecting to see a Successful unit test with stubbed debug method logging 'Message Logged'
I'm ignoring the syntax issue with defining $this->myclassObject at the class level, because I assume that's a typo from you creating this question.
I think that you have two problems:
You're overriding PHPUnit's ability to mock a class/interface's abstract/interface methods by specifying null in LoggerInterface's setMethods, which tells it not to mock anything
You're both disabling the Foo constructor and providing constructor args (in quotes as a scalar value of the variable name)
You also are referencing $logger which doesn't exist.
I'd also suggest that in your example, you don't need to partially mock Foo at all, since you're not mocking any of its functionality at this point. You can simply call new Foo($this->logger). I assume, however, that your example is cut down and you do need to partially mock other parts of the class, so will leave it for now.
Try this:
class FooTest extends TestCase
{
private $logger;
private $myclassObject;
protected function setUp()
{
$this->logger = $this->createMock('\Psr\Log\LoggerInterface');
$this->logger->expects($this->any())
->method('debug')
->willReturn('Message Logged');
$this->myclassObject = $this->getMockBuilder('\MyVendor\MyModule\Model\Foo')
->setConstructorArgs([$this->logger])
->getMock();
}
public function testBar()
{
$this->assertEquals($expected_result, $this->myclassObject->Bar());
}
}

Have setup method run only once

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).

Mockery forgetting byDefault setup when using shouldReceive for the same method but different arguments

We are experimenting a strange behaviour of Mockery (0.9.2) while tdd-ing a Symfony controller which makes use of several request parameters grabbed using the Request service. We are using PHPUnit (3.7) as testing framework.
The way we approach TDD is using setUp method for creating mocks and configuring them by using byDefault() so they can provide a neutral happy-flow scenario. Then, in each test method, we become specific about our expectations about mock behaviour.
I isolated the problem in a proof of concept test just for making the analysis easier. Here we go.
This is the test class itself:
class FooTest extends \PHPUnit_Framework_TestCase
{
private $request;
public function setUp()
{
$this->request = \Mockery::mock('Symfony\Component\HttpFoundation\Request');
$this->request->shouldReceive('get')->with('a')->andReturnNull()->byDefault();
$this->request->shouldReceive('get')->with('b')->andReturnNull()->byDefault();
}
public function test_bar_checks_request_a_parameter()
{
$this->request->shouldReceive('get')->with('a')->andReturn('a')->once();
$foo = new Foo($this->request);
$foo->bar();
}
}
And this is the tested class:
use Symfony\Component\HttpFoundation\Request;
class Foo
{
private $request;
function __construct(Request $request)
{
$this->request = $request;
}
public function bar()
{
$a = $this->request->get('a');
$b = $this->request->get('b');
}
}
In the test test_bar_checks_request_a_parameter I would expect bar() method to get 'a' when calling get('a') on the request mock while getting null when calling get('b') instead.
But, instead, we are getting this error:
No matching handler found for Mockery_0_Symfony_Component_HttpFoundation_Request::get("b").
Which seems to say that the Request mock forgot the setUp we made for get('b') call
shouldReceive('get')->with('b')->andReturnNull()->byDefault()
Is this a Mockery limitation? Is a bad approach on our side, a test smell maybe?
Thanks in advance
It's a Mockery limitation. When you set a new expectation for a method, Mockery disables all the byDefault() expectations for that method, even if they were set with distinct arguments.
There is an open issue regarding that:
https://github.com/padraic/mockery/issues/353
You can solve this by using an array of values and a function that will compute the return value each time. The trick is making the array accesible from the test methods so you can change the return values:
class FooTest extends \PHPUnit_Framework_TestCase
{
private $request;
private $get_return_values = array();
public function setUp()
{
$this->request = \Mockery::mock('Symfony\Component\HttpFoundation\Request');
$this->request->shouldReceive('get')->andReturnUsing(function($arg) {
return isset($this->get_return_values[$arg]) ? $this->get_return_values[$arg] : null;
});
}
public function test_bar_checks_request_a_parameter()
{
$this->get_return_values['a'] = 'a';
$foo = new Foo($this->request);
$foo->bar();
}
}

PHPUnit Mock Objects and Static Methods

I am looking for the best way to go about testing the following static method (specifically using a Doctrine Model):
class Model_User extends Doctrine_Record
{
public static function create($userData)
{
$newUser = new self();
$newUser->fromArray($userData);
$newUser->save();
}
}
Ideally, I would use a mock object to ensure that fromArray (with the supplied user data) and save were called, but that's not possible as the method is static.
Any suggestions?
Sebastian Bergmann, the author of PHPUnit, recently had a blog post about Stubbing and Mocking Static Methods. With PHPUnit 3.5 and PHP 5.3 as well as consistent use of late static binding, you can do
$class::staticExpects($this->any())
->method('helper')
->will($this->returnValue('bar'));
Update: staticExpects is deprecated as of PHPUnit 3.8 and will be removed completely with later versions.
There is now the AspectMock library to help with this:
https://github.com/Codeception/AspectMock
$this->assertEquals('users', UserModel::tableName());
$userModel = test::double('UserModel', ['tableName' => 'my_users']);
$this->assertEquals('my_users', UserModel::tableName());
$userModel->verifyInvoked('tableName');
I would make a new class in the unit test namespace that extends the Model_User and test that. Here's an example:
Original class:
class Model_User extends Doctrine_Record
{
public static function create($userData)
{
$newUser = new self();
$newUser->fromArray($userData);
$newUser->save();
}
}
Mock Class to call in unit test(s):
use \Model_User
class Mock_Model_User extends Model_User
{
/** \PHPUnit\Framework\TestCase */
public static $test;
// This class inherits all the original classes functions.
// However, you can override the methods and use the $test property
// to perform some assertions.
}
In your unit test:
use Module_User;
use PHPUnit\Framework\TestCase;
class Model_UserTest extends TestCase
{
function testCanInitialize()
{
$userDataFixture = []; // Made an assumption user data would be an array.
$sut = new Mock_Model_User::create($userDataFixture); // calls the parent ::create method, so the real thing.
$sut::test = $this; // This is just here to show possibilities.
$this->assertInstanceOf(Model_User::class, $sut);
}
}
Found the working solution, would to share it despite the topic is old.
class_alias can substitute classes which are not autoloaded yet (works only if you use autoloading, not include/require files directly).
For example, our code:
class MyClass
{
public function someAction() {
StaticHelper::staticAction();
}
}
Our test:
class MyClassTest
{
public function __construct() {
// works only if StaticHelper is not autoloaded yet!
class_alias(StaticHelperMock::class, StaticHelper::class);
}
public function test_some_action() {
$sut = new MyClass();
$myClass->someAction();
}
}
Our mock:
class StaticHelperMock
{
public static function staticAction() {
// here implement the mock logic, e.g return some pre-defined value, etc
}
}
This simple solution doesn't need any special libs or extensions.
Mockery's Alias functionality can be used to mock public static methods
http://docs.mockery.io/en/latest/reference/creating_test_doubles.html#creating-test-doubles-aliasing
Another possible approach is with the Moka library:
$modelClass = Moka::mockClass('Model_User', [
'fromArray' => null,
'save' => null
]);
$modelClass::create('DATA');
$this->assertEquals(['DATA'], $modelClass::$moka->report('fromArray')[0]);
$this->assertEquals(1, sizeof($modelClass::$moka->report('save')));
One more approach:
class Experiment
{
public static function getVariant($userId, $experimentName)
{
$experiment = self::loadExperimentJson($experimentName):
return $userId % 10 > 5; // some sort of bucketing
}
protected static function loadExperimentJson($experimentName)
{
// ... do something
}
}
In my ExperimentTest.php
class ExperimentTest extends \Experiment
{
public static function loadExperimentJson($experimentName)
{
return "{
"name": "TestExperiment",
"variants": ["a", "b"],
... etc
}"
}
}
And then I would use it like so:
public function test_Experiment_getVariantForExperiment()
{
$variant = ExperimentTest::getVariant(123, 'blah');
$this->assertEquals($variant, 'a');
$variant = ExperimentTest::getVariant(124, 'blah');
$this->assertEquals($variant, 'b');
}
Testing static methods is generally considered as a bit hard (as you probably already noticed), especially before PHP 5.3.
Could you not modify your code to not use static a method ? I don't really see why you're using a static method here, in fact ; this could probably be re-written to some non-static code, could it not ?
For instance, could something like this not do the trick :
class Model_User extends Doctrine_Record
{
public function saveFromArray($userData)
{
$this->fromArray($userData);
$this->save();
}
}
Not sure what you'll be testing ; but, at least, no static method anymore...

Categories