I am writing a basic PDO wrapper class and when I want to simulate the throwing of an exception by PDOStatement::prepare() using willThrowException() with the mock of PDOException in my unit test, the returned value of getMessage() is always and empty string instead of what I set up.
Here is how I tried it:
// WrapperClass.php
<?php
class WrapperClass
{
private $pdo;
private $error = '';
public function __construct(\PDO $pdo)
{
$this->pdo = $pdo;
}
public function save()
{
$sql = 'INSERT INTO ...';
try {
$this->pdo->prepare($sql);
// some value binding and executing the statement
} catch (\PDOException $pdoException) {
$this->error = $pdoException->getMessage();
}
}
public function getError()
{
return $this->error;
}
}
and my test:
// WrapperClassTest.php
<?php
class WrapperClassTest extends \PHPUnit_Framework_TestCase
{
/**
* #test
*/
public function save_saves_PDOException_message_in_error_property()
{
$pdoMock = $this->getMockBuilder('WrapperClass')
->disableOriginalConstructor()
->setMethods(['prepare'])
->getMock();
$pdoMock->expects($this->once())
->method('prepare')
->willThrowException($pdoExceptionMock);
$pdoExceptionMock = $this->getMockBuilder('\PDOException')
->setMethods(['getMessage'])
->getMock();
$message = 'Message from PDOException';
$pdoExceptionMock->expects($this->once())
->method('getMessage')
->willReturn($message);
$wrapperClass = new WrapperClass($pdoMock);
$wrapperClass->save();
$this->assertEquals($message, $wrapperClass->getError());
}
}
I also tried to replace ->willThrowException($pdoException) with ->will($this->throwException($pdoException)) but it does not work.
I noticed that if I replace ->willThrowException($pdoException) with ->willThrowException(new \PDOException('Message from PDOException')) it works but then I'm relying on the PDOException class instead of mocking it.
Any ideas?
Just 2 statements:
1) All exceptions in PHP 5.x extends base Exception and it defines 'getMessage' method as final:
final public string Exception::getMessage ( void )
2) PHPUnit silently do nothing when you try to mock final methods (you can see code that generate mocks here, canMockMethod returns false for final methods)
So
->setMethods(['getMessage'])
has no effect.
On the other side you don't really need to mock exceptions because they are value objects. Passing new PDOException('Message from PDOException') is pretty ok.
Related
I'm new to PHPUnit, and unit testing in general. I can't seem to find a clear tutorial or resource on how best to test:
Passing no argument fails.
How to pass an argument for a constructor test.
Passing an empty argument results in the expected exception.
How would I approach testing this constructor?
<?php
class SiteManagement {
public function __construct (array $config) {
// Make sure we actually passed a config
if (empty($config)) {
throw new \Exception('Configuration not valid', 100);
}
// Sanity check the site list
if (empty($config['siteList'])) {
throw new \Exception('Site list not set', 101);
}
}
}
The example 2.11 of the PHPUnit documentation shows how to test exceptions.
For your specific class, it would be something like this:
$this->expectException(Exception::class);
$object = new SiteManagement([]);
You shouldn't test if the method fails without arguments unless those arguments are optional. This would be out of the scope for a unit test.
A good test battery for your class would be:
/** #test */
public function shouldFailWithEmptyConfig(): void
{
$config = [];
$this->expectException(\Exception::class);
$this->expectExceptionMessage('Configuration not valid');
$this->expectExceptionCode(100);
new SiteManagement($config);
}
/** #test */
public function shouldFailWithoutSiteListConfig(): void
{
$config = ['a config'];
$this->expectException(\Exception::class);
$this->expectExceptionMessage('Site list not set');
$this->expectExceptionCode(101);
new SiteManagement($config);
}
/** #test */
public function shouldMakeAFuncionality(): void
{
$config = [
'siteList' => '',
];
$siteManagement = new SiteManagement($config);
self::assertSame('expected', $siteManagement->functionality());
}
I have this exception:
throw new \DomainException(__("message d'erreur"));
I don't know how can I do the test of this instruction by PHPUnit: __().
Im sure there are other ways which i'm not aware of but I would do it like the following, if you downvote please comment why. If I learn something new I'm happy.
Ok, depending on whether the application expects the function to be defined to work or whether its expected but not part of the current test you could mock it:
Using: php-mock/php-mock-phpunit
<?php
use PHPUnit\Framework\TestCase;
class SomeTest extends TestCase
{
use \phpmock\phpunit\PHPMock;
/**
* __() function must exist or test fails.
*/
public function testDoubleUndescoreFunction()
{
$this->assertTrue(function_exists('__'), '__() function does not exist');
}
// or
/**
* __() function will be mocked.
*/
public function testDoubleUndescoreFunction()
{
// mock __() function
if (!function_exists('__')) {
$mocks['__'] = $this->getFunctionMock(__NAMESPACE__, "__");
$mocks['__']->expects($this->any())->willReturnCallback(
function ($value) {
$this->assertInternalType('string', $value);
return $value;
}
);
}
// test
$this->assertTrue(function_exists('__'), '__() function does not exist');
try {
//
// run test which will cause the exception
} catch (\Exception $e) {
$this->assertInstanceOf('DomainException', $e);
$this->assertEquals('message d\'erreur', $e->getMessage());
}
}
}
?>
I'm new to unit testing and have come across something I don't understand when using returnValueMap() in PHPUnit. I've been googling for days now ...
Consider this code under test;
public function __construct(EntityManager $entityManager, AuditLog $auditLog) {
$this->entityManager = $entityManager;
$this->auditLog = $auditLog;
}
public function updateSomeId($oldId, $newId)
{
$repositories = ['repo1', 'repo2', 'repo3'];
foreach ($repositories as $repository) {
try {
$this->entityManager->getRepository($repository)
->updateSomeId($oldId, $newId);
} catch (RepositoryException $e) {
throw new SomeOtherException($e->getMessage(), $e->getCode(), $e);
}
}
}
The unit test code;
... code removed for brevity
$repoMaintenance = new RepoMaintenance(
$this->getEntityManagerMock(),
$this->getAuditLogMock()
);
$this->assertTrue($repoMaintenance->updateSomeId(
$this->oldId,
$this->newId
));
/**
* #return EntityManager
*/
private function getEntityManagerMock()
{
$entityManagerMock = $this->getMockBuilder(EntityManager::class)
->disableOriginalConstructor()
->getMock();
$entityManagerMock
->method('getRepository')
->willReturn($this->returnValueMap($this->getEntityManagerReturnValueMap()));
return $entityManagerMock;
}
/**
* #return array
*/
private function getEntityManagerReturnValueMap()
{
return [
['repo1', $this->getRepo1Mock()],
['repo2', $this->getRepo2Mock()],
['repo3', $this->getRepo3Mock()],
];
}
/**
* #return Repo1
*/
private function getRepo1Mock()
{
return $this->getMockBuilder(Repo1::class)
->disableOriginalConstructor()
->getMock();
}
... Code removed for brevity
When the unit test is run, the following fatal error is returned;
PHP Fatal error: Call to undefined method PHPUnit_Framework_MockObject_Stub_ReturnValueMap::updateSomeId()
I've previously used mocks in return value maps with no issue accessing methods in a public context. The difference is I'm attempting to mock __construct() variables, which are set to private access within the SUT.
What am I missing? The problem (I would naively guess) is with the private access level of the members being mocked.
Is there a way to unit test this code? I don't want to hit the database at any point and this is the reason for mocking the calls to it.
You should have will($this->returnValueMap... instead of willReturn($this->returnValueMap...
I am trying to test the below class using PHPUnit
class stripe extends paymentValidator {
public $apiKey;
public function __construct ($apiKey){
$this->apiKey = $apiKey;
}
public function charge($token) {
try {
return $this->requestStripe($token);
} catch(\Stripe\Error\Card $e) {
echo $e->getMessage();
return false;
}
}
public function requestStripe($token) {
// do something
}
}
My test scripts is like the below:
class paymentvalidatorTest extends PHPUnit_Framework_TestCase
{
/**
* #test
*/
public function test_stripe() {
// Create a stub for the SomeClass class.
$stripe = $this->getMockBuilder(stripe::class)
->disableOriginalConstructor()
->setMethods(['requestStripe', 'charge'])
->getMock();
$stripe->expects($this->any())
->method('requestStripe')
->will($this->returnValue('Miaw'));
$sound = $stripe->charge('token');
$this->assertEquals('Miaw', $sound);
}
}
With my test script I was expecting the test double of stripe::charge() method will do exactly as the defined in the original class and the stripe::requestStripe() will return 'Miaw'. Therefore, $stripe->charge('token') should also return 'Miaw'. However, when I run the test I get:
Failed asserting that null matches expected 'Miaw'.
How should I fix this ?
Where you're calling setMethods, you're telling PHPUnit that the mock class should mock the behaviour of those methods:
->setMethods(['requestStripe', 'charge'])
In your case it looks like you want to partially mock the class, so that requestStripe() returns Miaw, but you want charge to run its original code - you should just remove charge from the mocked methods:
$stripe = $this->getMockBuilder(stripe::class)
->disableOriginalConstructor()
->setMethods(['requestStripe'])
->getMock();
$stripe->expects($this->once())
->method('requestStripe')
->will($this->returnValue('Miaw'));
$sound = $stripe->charge('token');
$this->assertEquals('Miaw', $sound);
While you're at it you may as well specify how many times you expect requestStripe() to be called - it's an extra assertion with no extra effort, as using $this->any() doesn't provide you with any added benefit. I've included using $this->once() in the example.
I'm attempting to write PHPUnit tests for an Email abstraction class i'm using. The class interacts with the Mailgun API but I don't want to touch this in my test, I just want to return the response I would expect from Mailgun.
Within my test I have a setup method:
class EmailTest extends PHPUnit_Framework_TestCase
{
private $emailService;
public function setUp()
{
$mailgun = $this->getMockBuilder('SlmMail\Service\MailgunService')
->disableOriginalConstructor()
->getMock();
$mailgun->method('send')
->willReturn('<2342423#sandbox54533434.mailgun.org>');
$this->emailService = new Email($mailgun);
parent::setUp();
}
public function testEmailServiceCanSend()
{
$output = $this->emailService->send("me#test.com");
var_dump($output);
}
}
This is the basic outline of the email class
use Zend\Http\Exception\RuntimeException as ZendRuntimeException;
use Zend\Mail\Message;
use SlmMail\Service\MailgunService;
class Email
{
public function __construct($service = MailgunService::class){
$config = ['domain' => $this->domain, 'key' => $this->key];
$this->service = new $service($config['domain'], $config['key']);
}
public function send($to){
$message = new Message;
$message->setTo($to);
$message->setSubject("test subject");
$message->setFrom($this->fromAddress);
$message->setBody("test content");
try {
$result = $this->service->send($message);
return $result;
} catch(ZendRuntimeException $e) {
/**
* HTTP exception - (probably) triggered by network connectivity issue with Mailgun
*/
$error = $e->getMessage();
}
}
}
var_dump($output); is currently outputting NULL rather than the string i'm expecting. The method send i'm stubbing in the mock object has a dependency through an argument, and when I call $mailgun->send() directly it errors based on this so I wonder if this is what is failing behind the scenes. Is there a way to pass this argument in, or should I approach this a different way?
It is strange it does not throw an exception in Email::__construct.
The expected parameter is a string, and the MailgunService object is instantiated within the email constructor. In your test you are passing the object, so I would expect and error at line
$this->service = new $service($config['domain'], $config['key']);
What you need is:
class Email
{
public function __construct($service = null){
$config = ['domain' => $this->domain, 'key' => $this->key];
$this->service = $service?: new MailgunService($config['domain'], $config['key']);
}
Also, it may not be a good idea to catch an exception and return nothing in Email::send.