I'm having difficulty mocking the PDO object with PHPUnit.
There doesn't seem to be much information on the web about my problem but from what I can gather:
PDO has 'final' __wakeup and
__sleep methods that prevent it from being serialised.
PHPunit's mock object implementation serialises the object at some point.
The unit tests then fail with a PHP error generated by PDO when this occurs.
There is a feature meant to prevent this behavior, by adding the following line to your unit test:
class MyTest extends PHPUnit_Framework_TestCase
{
protected $backupGlobals = FALSE;
// ...
}
Source: http://sebastian-bergmann.de/archives/797-Global-Variables-and-PHPUnit.html
This isnt working for me, my test still produces an error.
Full test code:
class MyTest extends PHPUnit_Framework_TestCase
{
/**
* #var MyTest
*/
private $MyTestr;
protected $backupGlobals = FALSE;
/**
* Prepares the environment before running a test.
*/
protected function setUp()
{
parent::setUp();
}
/**
* Cleans up the environment after running a test.
*/
protected function tearDown()
{
parent::tearDown();
}
public function __construct()
{
$this->backupGlobals = false;
parent::__construct();
}
/**
* Tests MyTest->__construct()
*/
public function test__construct()
{
$pdoMock = $this->getMock('PDO', array('prepare'), array(), '', false);
$classToTest = new MyTest($pdoMock);
// Assert stuff here!
}
// More test code.......
Any PHPUnit pro's give me a hand?
Thanks,
Ben
$backupGlobals does not help you, because this error comes from elsewhere. PHPUnit 3.5.2 (possibly earlier versions as well) has the following code in PHPUnit/Framework/MockObject/Generator.php
if ($callOriginalConstructor &&
!interface_exists($originalClassName, $callAutoload)) {
if (count($arguments) == 0) {
$mockObject = new $mock['mockClassName'];
} else {
$mockClass = new ReflectionClass($mock['mockClassName']);
$mockObject = $mockClass->newInstanceArgs($arguments);
}
} else {
// Use a trick to create a new object of a class
// without invoking its constructor.
$mockObject = unserialize(
sprintf(
'O:%d:"%s":0:{}',
strlen($mock['mockClassName']), $mock['mockClassName']
)
);
}
This "trick" with unserialize is used when you ask getMock to not execute the original constructor and it will promptly fail with PDO.
So, how do work around it?
One option is to create a test helper like this
class mockPDO extends PDO
{
public function __construct ()
{}
}
The goal here is to get rid of the original PDO constructor, which you do not need. Then, change your test code to this:
$pdoMock = $this->getMock('mockPDO', array('prepare'));
Creating mock like this will execute original constructor, but since it is now harmless thanks to mockPDO test helper, you can continue testing.
The best I can think of is to use runkit and redefine the two final methods as protected using runkit_function_redefine.
Dont for get to enable the runkit.internal_override setting in php.ini.
And as ever, as with eval, if runkit seems like the answer, the question is probably wrong :)
You are instantiating your test case in your test case?
$classToTest = new MyTest($pdoMock);
Right now, you are essentially testing your test case. It should be more something like:
$classToTest = new My($pdoMock);
Related
I am trying to write a unit test for a protected method which I am aware that I can use reflection class to achieve this. The problem is that, this protected method calls two private methods and I need to mock those private methods (I have my reasons for this). Is this even possible?
Here is my class:
class MyClass
{
protected function myProtectedMethod(string $argOne, int $argTwo)
{
$privateMethodOneValue = $this->privateMethodOne($argOne);
$privateMethodTwoValue = $this->privateMethodTwo($argTwo);
// Some more logic below that is unrelated to the question
}
private function privateMethodOne(string $arg): string
{
// does some laravel specific stuff that can't be unit tested in PHPUnit\Framework\TestCase
// this is why it was abstracted out from the protected method, to make unit testing possible
}
private function privateMethodTwo(int $arg): int
{
// does some laravel specific stuff that can't be unit tested in PHPUnit\Framework\TestCase
// this is why it was abstracted out from the protected method, to make unit testing possible
}
}
In my test, I have something like this:
use PHPUnit\Framework\TestCase;
class MyClassTest extends TestCase
{
public function testMyProtectedMethod()
{
$mmyMockClass = $this->getMockBuilder(Controller::class)
->onlyMethods(['privateMethodOne', 'privateMethodTwo'])
->getMock();
$reflectionClass = new \ReflectionClass($mmyMockClass);
$privateMethodOne = $reflectionClass->getMethod('privateMethodOne');
$privateMethodOne->setAccessible(true);
$privateMethodTwo = $reflectionClass->getMethod('privateMethodTwo');
$privateMethodTwo->setAccessible(true);
$myProtectedMethod = $reflectionClass->getMethod('myProtectedMethod');
$myProtectedMethod->setAccessible(true);
$mockArgOne = 'some argument string';
$mockArgTwo = 99999;
$privateMethodOneResult = 'some result string';
$privateMethodTwoResult = 88888;
$mmyMockClass->expects($this->once())
->method('privateMethodOne')
->with($mockArgOne)
->willReturn($privateMethodOneResult);
$mmyMockClass->expects($this->once())
->method('privateMethodTwo')
->with($mockArgTwo)
->willReturn($privateMethodTwoResult);
$result = $myProtectedMethod->invoke($reflectionClass, $mockArgOne, $mockArgTwo);
// some assertions here
}
}
but obviously this doesn't work. I am getting errors for the private methods I am trying to mock. Here is what the error looks like:
Trying to configure method "privateMethodOne" which cannot be configured because it does not exist, has not been specified, is final, or is static
I've read a lot of articles, posts about this and I know that generally it's a bad practice to try to unit test private methods, and/or it's a bad design if you find yourself that you have to test it. I understand all of that and if there is more about that I need to read that's welcome as well, but, I, at least am trying to understand if this is even possible, and would love to learn how if it is.
Thank you all in advance.
$result = $myProtectedMethod->invoke($reflectionClass, $mockArgOne, $mockArgTwo);
should be
$result = $myProtectedMethod->invoke($mmyMockClass, $mockArgOne, $mockArgTwo);
For more information how to use "invoke" method here. https://www.php.net/manual/en/reflectionmethod.invoke.php
Suppose I have this class:
class SomeClass
{
// Top level function
public function execute($command)
{
// Get output from system tool
$output = $this->runTool($command);
// Check output for errors
if ($this->hasError($output))
return false;
// And parse success response from tool
return $this->parseOutput($output);
}
// There we're make a call to system
private function runTool($command)
{
return `/some/system/tool $command`;
}
[...]
}
I do not want to run system tool in my test, I want to replace a system call with predefined output.
So, the question is - should I create another class, move system call in it and mock that class in the test, or I can mock only that function of class which I will test?
Sure, both approaches will work, but which of them will be serve testing purposes better?
If you follow the single responsibility principle, you won't have this problem. Your class does not need to know how system calls are made, so you will have to use another class. You mock that.
IMO, in most cases when you need to mock protected or private methods, they do stuff that should be into another class and be mocked.
I would say it really depends on your infrastructure. Sometimes it is better to use Mock, sometimes Stub.
If the case is, that the class you want to test contains this unwanted method - use Mock and mock only this one function. That will make you sure, that any changes made to that class will be handled by the test.
If the unwanted function is a part of i.e. injected service or another class, which is not the domain of this particular test, you can create a stub.
You can't test private method, you can use a workaround and invoke it via reflection as described in this article and discussed in this SO QUESTION
But i suggest you to change the method visibility to protected and mock only the behaviour of the runTool method.
As example, suppose the following modified version of your class (i don't know how other method work so i suppose that you want to test their behaviour and take this implementation as example):
<?php
namespace Acme\DemoBundle\Service;
class SomeClass
{
// Top level function
public function execute($command)
{
// Get output from system tool
$output = $this->runTool($command);
// Check output for errors
if ($this->hasError($output))
return false;
// And parse success response from tool
return $this->parseOutput($output);
}
// There we're make a call to system
protected function runTool($command)
{
return `/some/system/tool $command`;
}
private function hasError($output)
{
return $output == "error";
}
private function parseOutput($output)
{
return json_decode($output);
}
}
As suppose the following test case:
<?php
namespace Acme\DemoBundle\Tests;
class SomeClassTest extends \PHPUnit_Framework_TestCase {
public function testCommandReturnError()
{
$mock = $this->getMockBuilder('Acme\DemoBundle\Service\SomeClass')
->setMethods(array('runTool'))
->disableOriginalConstructor()
->getMock()
;
$mock
->expects($this->exactly(1))
->method('runTool')
->with("commandName")
->will($this->returnValue("error"));
$this->assertFalse($mock->execute("commandName"));
}
public function testCommandReturnCorrectValue()
{
$mock = $this->getMockBuilder('Acme\DemoBundle\Service\SomeClass')
->setMethods(array('runTool'))
->disableOriginalConstructor()
->getMock()
;
$mock
->expects($this->exactly(1))
->method('runTool')
->with("commandName")
->will($this->returnValue('{"title":"myTitle"}'));
$returnValue = $mock->execute("commandName");
$this->assertEquals("myTitle", $returnValue->title);
}
}
Hope this help
Lately I'm giving a try to phpspec. It works great, but I have got a problem with testing command handlers. For example in PHPUnit I test it that way:
/**
* #test
*/
public function it_should_change_an_email()
{
$this->repository->add($this->employee);
$this->handler->changeEmail(
new ChangeEmailCommand(
$this->employee->username()->username(),
'new#email.com'
)
);
Asserts::assertEquals(new Email('new#email.com'), $this->employee->email());
}
and setup:
protected function setUp()
{
$this->repository = new InMemoryEmployeeRepository();
$this->createEmployee();
$this->handler = new EmployeeCommandHandler($this->repository);
}
The main point is that this test make assertions on the Employee object to check if CommandHandler is working good. But in phpspec I can't make assertion on different object than the specifying one, in this case I can only make assertion on my CommandHandler. So how I can test a command handler in phpspec?
EDIT
Maybe spies are the way to go:
class EmployeeCommandHandlerSpec extends ObjectBehavior
{
const USERNAME = 'johnny';
/** #var EmployeeRepository */
private $employeeRepository;
public function let(EmployeeRepository $employeeRepository)
{
$this->employeeRepository = $employeeRepository;
$this->beConstructedWith($employeeRepository);
}
public function it_changes_the_employee_email(Employee $employee)
{
$this->givenEmployeeExists($employee);
$this->changeEmail(
new ChangeEmailCommand(self::USERNAME, 'new#email.com')
);
$employee->changeEmail(new Email('new#email.com'))->shouldHaveBeenCalled();
}
private function givenEmployeeExists(Employee $employee)
{
$this->employeeRepository->employeeWithUsername(new EmployeeUsername(self::USERNAME))
->shouldBeCalled()
->willReturn($employee);
}
}
Employee class I've already speced. So, maybe, in command handler it'll be enough to just check if the method of the Employee has been called. What do you think about it? Am I going in good direction?
Messaging
Indeed, you shouldn't verify the state, but expect certain interactions between objects. That's what OOP is about afterall - messaging.
The way you've done it in PHPUnit is state verification. It forces you to expose the state as you need to provide a "getter", which is not always desired. What you're interested in is that Employee's email was updated:
$employee->updateEmail(new Email('new#email.com'))->shouldBeCalled();
The same can be achieved with spies if you prefer:
$employee->updateEmail(new Email('new#email.com'))->shouldHaveBeenCalled();
Command/Query Separation
We usually only need to state our expectations against methods that have side effects (command methods from Command/Query separation). We mock them.
Query methods do not need to be mocked, but stubbed. You don't really expect that EmployeeRepository::employeeWithUsername() should be called. Doing so we're making assumptions about implementation which in turn will make refactoring harder. All you need is stubbing it, so if a method is called it returns a result:
$employeeRepository->employeeWithUsername(new EmployeeUsername(self::USERNAME))
->willReturn($employee);
Full example
class EmployeeCommandHandlerSpec extends ObjectBehavior
{
const USERNAME = 'johnny';
public function let(EmployeeRepository $employeeRepository)
{
$this->beConstructedWith($employeeRepository);
}
public function it_changes_the_employee_email(
EmployeeRepository $employees, Employee $employee
) {
$this->givenEmployeeExists($employees, $employee);
$this->changeEmail(
new ChangeEmailCommand(self::USERNAME, 'new#email.com')
);
$employee->changeEmail(new Email('new#email.com'))->shouldHaveBeenCalled();
}
private function givenEmployeeExists(
EmployeeRepository $employees, Employee $employee
) {
$employees->employeeWithUsername(new EmployeeUsername(self::USERNAME))
->willReturn($employee);
}
}
I am trying to test methods from the following class I have written (there are more functions than what is shown, basically, one function for each is_*() method):
class Validate {
private static $initialized = false;
/**
* Construct won't be called inside this class and is uncallable from the outside. This prevents
* instantiating this class. This is by purpose, because we want a static class.
*/
private function __construct() {}
/**
* If needed, allows the class to initialize itself
*/
private static function initialize()
{
if(self::$initialized) {
return;
} else {
self::$initialized = true;
//Set any other class static variables here
}
}
...
public static function isString($string) {
self::initialize();
if(!is_string($string)) throw new InvalidArgumentException('Expected a string but found ' . gettype($string));
}
...
}
When I test if the methods throw an exception on invalid input, it works great! However, when I test if the method works as expected, PHPUnit complains because I have no assert in the test. The specific error is:
# RISKY This test did not perform any assertions
However, I don't have any value to assert against so I'm not sure how to overcome this.
I've read some about testing static methods, but that mostly seems to cover dependencies between static methods. Further, even non-static methods could have no return value, so, how to fix this?
For reference, my test code:
class ValidateTest extends PHPUnit_Framework_TestCase {
/**
* #covers ../data/objects/Validate::isString
* #expectedException InvalidArgumentException
*/
public function testIsStringThrowsExceptionArgumentInvalid() {
Validate::isString(NULL);
}
/**
* #covers ../data/objects/Validate::isString
*/
public function testIsStringNoExceptionArgumentValid() {
Validate::isString("I am a string.");
}
}
Test void function with assertNull:
/**
* #covers ../data/objects/Validate::isString
*/
public function testIsStringNoExceptionArgumentValid() {
$this->assertNull( Validate::isString("I am a string.") );
}
To prevent the warning about the assertions you can use the #doesNotPerformAssertions annotation as explained in the documentation: https://phpunit.de/manual/current/en/appendixes.annotations.html#idp1585440
Or if you prefer code over annotation:
$this->doesNotPerformAssertions();
One solution I have come upon is the following, based on example 2.12 from chapter 2 of PHPUnit. It feels a little hacky to me, but it's the best I've found so far. Also, based on this PHPUnit Gitub issue discussion, it seems several other people want this feature but that there are no plans to implement it.
Change testIsStringNoExceptionArgumentValid() to the following:
/**
* #covers ../data/objects/Validate::isString
*/
public function testIsStringNoExceptionArgumentValid() {
try {
Validate::isString("I am a string.");
} catch (InvalidArgumentException $notExpected) {
$this->fail();
}
$this->assertTrue(TRUE);
}
If you want to test a void function you only need to run it without any assertion.
If it there is any issue it will throw an exception and test will fails. No need to put $this->assertTrue(TRUE); as you are not running an assertion and having assertions is not required to test your code.
You will get a message like
Time: 7.39 seconds, Memory: 16.00 MB
OK (1 test, 0 assertions)
Process finished with exit code 0
This is probably an easy one for some of you. I'm trying to test a protected method on a small DB connection class I have.
Relevant code is as follows:
class DbConnect{
/**
* Connexion MSSQL local
*/
protected function localConnect($localconfig){
$connectionInfo = array("UID" => $localconfig->uid,
"PWD" =>$localconfig->pwd,
"Database"=> $localconfig->DB);
$this->localConnection = sqlsrv_connect($localconfig->serverName,
$connectionInfo);
if( $this->localConnection === false ){
$sql_error = sqlsrv_errors();
throw new DBException("Error in DB Connection.\r\n
SQL ERROR:" . $sql_error);
}
}
}
To test the method, I had the bright idea (probably from a post here somewhere) to subclass and call from there. I created a subclass, right at bottom of my test file. I obviously could not override the visibility of the method to public, so decided another approach in the stub: declare a public method that calls the parent's protected localConnect method:
class DBConnectStub extends DBconnect{
public function callLocalConnect($localConfig){
parent::localConnect($localConfig);
}
}
My test now looks like this:
/**
* #expectedException DBException
*/
public function test_localConnectError(){
$localconfig = (object) array ( 'serverName' => 'nohost',
'uid' => 'nouid',
'pwd' => 'noPwd',
'DB' => 'noDB'
);
$db = DbConnectStub::getInstance($localconfig, array());
$db->callLocalConnect($localConfig);
unset($db);
}
The weird part, when I run the test, php spits out:
Fatal error: Call to undefined method DbConnect::callLocalConnect() in C:\tirelinkCRMsync\test
\tirelinkCRMSync\DBConnectTest.php on line 82.
The object is properly instanciated, but why is the method not defined, surely there is a detail that has eluded me. Is this approach valid or is there a better way?
I'm trying to test a protected method [...]
DON'T
It's as simple as that. Just don't. Protected methods are not part of the classes public API and therefore you should not make assumptions on how they work when trying to make sure your class works.
You should be able to change your code (implementation of your public functions) without adapting your tests. Thats what your tests are made for, so that you can change your code and you are sure that it still works. You can't be sure your code still works like before when you change your code and your tests at the same time!
See: Sebastian Bergmann -Testing Your Privates.html
So: Just because the testing of protected and private attributes and methods is possible does not mean that this is a "good thing".
and: Best practices to test protected methods with PHPUnit - on abstract classes
What this post also mentions is to just use
$method = new ReflectionMethod(
'Foo', 'doSomethingPrivate'
);
$method->setAccessible(TRUE);
Which is easier than to create a subclass for every method you want to test.
Pedantic side node:
Imho it should be $this->localConnect and not parent::localConnect because parent:: is only for calling the same method of the parent class. (Doesn't matter much, just confusing, for me at least).
This may be a stupid question, but did you override DbConnectStub::getInstance for it to return a Stub instance ?
class DBConnectStub extends DBconnect{
public static function getInstance ()
{
//whatever process to create the instance (and not the parent method call that will return a DBConnect instance)
}
public function callLocalConnect($localConfig){
parent::localConnect($localConfig);
}
}