PHP Unit mocked objects types - php

What is the correct way of setting a type for mocked objects?
Example code:
/**
* #dataProvider getTestDataProvider
* #throws Exception
*/
public function testExampleData(
Request $request,
Response $expected,
SomeClass $someClassMock
): void {
$result = $someClassMock->getData($request);
$this->assertEquals($expected, $result);
}
In this example the type of $someClassMock is class SomeClass. Also there is a type called MockObject which is also working properly, but it messes up the autocompletion of functions inside that class.
Which types should I use on these mocked objects? Real object class or MockObject?

What I do to make sure the auto completion works for the mocked class as well as the MockObject, is tell php that it can be either class. This adds the auto complete for both and also makes it quite understandable for anyone reading the code that it is a mock and what object it is mocking.
In your case it would look like this:
/**
* #dataProvider getTestDataProvider
* #throws Exception
*/
public function testExampleData(
Request $request,
Response $expected,
SomeClass|MockObject $someClassMock // <-- Change made here
): void {
$result = $someClassMock->getData($request);
$this->assertEquals($expected, $result);
}
You will still be passing in the MockObject, but your IDE will not complain about any unknown functions.
EDIT: To make it work with PHP versions before 8, I would suggest something like this (minimal example):
class ExampleTest extends TestCase
{
private someClass | MockObject $someClassMock;
public function setUp(): void
{
$this->someClassMock = $this->createMock(SomeClass::Class);
}
public function testMe()
{
$this->someClassMock->expects($this->once())->method('getData')->willReturn('someStuff');
}
}
In this scenario the variable $someClassMock is declared as both types and you can use it throughout your test with autocompletion for both of them. This should also work with your data provider although you might have to rewrite it slightly. I didn't include it in my example to keep it simple.

Related

PhpStorm not finding methods in objects created by late static binding by parent abstract class

In my PHP application I have a base class for all database objects, which is further extended by specific model classes. It goes on like this (simplified):
abstract class Model extends Collection {
(...)
function __construct(string $primary_key, string $value) {
$data = MysqlDB::instance() -> select(static::TABLE, implode(',', static::COLUMNS), "$primary_key=\"$value\"");
if (count($data) != 1)
throw new ModelException('Select condition uncertain');
parent::__construct($data[0]);
}
public static function getById(string $id) : ?Model {
try {
return new static('id', $id);
} catch (ModelException $e) {
return NULL;
}
}
(...)
}
The point is, I use the getById function on child classes to obtain the needed objects. However, since the return type of getById method is Model, PhpStorm can't figure out what methods the objects has and highlights the ones I use as an error. Consequently, no type-hinting available.
For instance, assuming that final class User extends Model and that class User has method sendNotification, this kind of code:
User::getById($id) -> sendNotification($param1, $param2, ...)
has sendNotification yellowed out as though it did not exist.
I know this isn't that much of an issue, but it's really irritating in terms of that I get distracted by constant redundant warnings and that I can't use type-hinting in such a usage case.
Try with actual PHPDoc for your getById() where you specify dynamic type (e.g. #return static or #return $this).
That's the most common way of providing such "this class and its' children" typehint.
Another possible option is kind of the same .. but using PHP 7.1 functionality (so no PHPDoc blocks).
Try using self as return type instead of Model. I mean here:
public static function getById(string $id) : ?Model {
use this instead:
public static function getById(string $id) : ?self {
But for this one you should use PhpStorm 2017.2 EAP build as 2017.1.x has issues with that (see https://stackoverflow.com/a/44806801/783119 as an example).
There are three ways to achieve this, none of them has anything to do with PHPStorm.
First overwrite the method in every child with the proper return type:
/**
* #return User
*/
public static function getById(string $id) : ?Model { return parent::getById($id); }
Second add all possible children to the return tag of the abstract class.
/**
* #return Model|User|...
*/
public static function getById(string $id) : ?Model { /* ... */ }
Third add a type hint just in place:
/** #var User $user */
$user = User::getById($id);
$user->sendNotification($param1, $param2, ...);
These are not nice and hard to maintain. But there is no "setting" in PHPStorm for that.

Testing command handler with phpspec

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);
}
}

How to PHPUnit test a method with no return value?

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

Overriding functions incompatibility

TL;DR
I want to override offsetSet($index,$value) from ArrayObject like this: offsetSet($index, MyClass $value) but it generates a fatal error ("declaration must be compatible").
What & Why
I'm trying to create an ArrayObject child-class that forces all values to be of a certain object. My plan was to do this by overriding all functions that add values and giving them a type-hint, so you cannot add anything other than values of MyClass
How
First stop: append($value);
From the SPL:
/**
* Appends the value
* #link http://www.php.net/manual/en/arrayobject.append.php
* #param value mixed <p>
* The value being appended.
* </p>
* #return void
*/
public function append ($value) {}
My version:
/**
* #param MyClass $value
*/
public function append(Myclass $value){
parent::append($value);
}
Seems to work like a charm.
You can find and example of this working here
Second stop: offsetSet($index,$value);
Again, from the SPL:
/**
* Sets the value at the specified index to newval
* #link http://www.php.net/manual/en/arrayobject.offsetset.php
* #param index mixed <p>
* The index being set.
* </p>
* #param newval mixed <p>
* The new value for the index.
* </p>
* #return void
*/
public function offsetSet ($index, $newval) {}
And my version:
/**
* #param mixed $index
* #param Myclass $newval
*/
public function offsetSet ($index, Myclass $newval){
parent::offsetSet($index, $newval);
}
This, however, generates the following fatal error:
Fatal error: Declaration of
Namespace\MyArrayObject::offsetSet() must be
compatible with that of ArrayAccess::offsetSet()
You can see a version of this NOT working here
If I define it like this, it is fine:
public function offsetSet ($index, $newval){
parent::offsetSet($index, $newval);
}
You can see a version of this working here
Questions
Why doesn't overriding offsetSet() work with above code, but append() does?
Do I have all the functions that add objects if I add a definition of exchangeArray() next to those of append() and offsetSet()?
abstract public void offsetSet ( mixed $offset , mixed $value )
is declared by the ArrayAccess interface while public void append ( mixed $value ) doesn't have a corresponding interface. Apparently php is more "forgiving"/lax/whatever in the latter case than with interfaces.
e.g.
<?php
class A {
public function foo($x) { }
}
class B extends A {
public function foo(array $x) { }
}
"only" prints a warning
Strict Standards: Declaration of B::foo() should be compatible with A::foo($x)
while
<?php
interface A {
public function foo($x);
}
class B implements A {
public function foo(array $x) { }
}
bails out with
Fatal error: Declaration of B::foo() must be compatible with A::foo($x)
APIs should never be made more specific.
In fact, I consider it a bug that append(Myclass $value) isn't a fatal error. I consider the The fatal error on your offsetSet() as correct.
The reason for this is simple:
function f(ArrayObject $ao) {
$ao->append(5); //Error
}
$ao = new YourArrayObject();
With an append with a type requirement, that will error. Nothing looks wrong with it though. You've effectively made the API more specific, and references to the base class are no longer able to be assumed to have the expected API.
What is basically comes down to is that if an API is made more specific, that sub class is no longer compatible with it's parent class.
This odd disparity can be seen with f: it allows you to pass a Test to it but will then fail on the $ao->append(5) execution. If a echo 'hello world'; were above it, that would execute. I consider that incorrect behavior.
In a language like C++, Java or C#, this is where generics would come into play. In PHP, I'm afraid there's not a pretty solution to this. Run time checks would be nasty and error prone, and rolling your own class would completely obliterate the advantages of having ArrayObject as the base class. Unfortunately, the desire to have ArrayObject as the base class is also the problem here. It stores mixed types, so your subclasses must store mixed types as well.
You could perhaps implement that ArrayAccess interface in your own class and clearly mark that the class is only meant to be used with a certain type of object. That would still be a bit clumsy though, I fear.
Without generics, there's not a way to have a generalized homogeneous container without runtime instanceof-style checks. The only way would be to have a ClassAArrayObject, ClassBArrayObject, etc.

PHPUnit provider not working with dependencies

I'm using PHPUnit 3.4.9, but I'm having some problems with the #depends annotation. It works like in the examples, but breaks when the producer reliers on a provider. I don't know if this is meant to work or not, but my code is basically in the form:
<?php
class StackTest extends PHPUnit_Framework_TestCase
{
/**
* #dataProvider provider
*/
public function testEmpty ($data)
{
$stack = array();
$this->assertTrue(empty($stack));
return $stack;
}
/**
* #depends testEmpty
*/
public function testPush (array $stack)
{
array_push($stack, 'foo');
$this->assertEquals('foo', $stack[count($stack)-1]);
$this->assertFalse(empty($stack));
return $stack;
}
/**
* #depends testPush
*/
public function testPop (array $stack)
{
$this->assertEquals('foo', array_pop($stack));
$this->assertTrue(empty($stack));
}
public function provider ()
{
return array(
// Some testing data here...
);
}
}
The code above is just an example, but shows what my code's structure is like. When ran, it skips the consumer tests, acting as though the producer had failed. I'm expecting that for every set of testing data in the provider, the producer will be run with that data, and all of its consumer correspondingly run.
Since the question is already 2 days old i give it a shot:
It doesn't seem to work the way you want it to.
#depends just looks if a test with the name provided has run and gets the result. It doesn't even know or care about the #annotations of said test.
I'd guess (haven't dug deep enough into the phpunit source to be 100% sure) tests with #depends are run as "group of tests" internally and not as a single one so there is not test named "testEmpty" and the depends fails.
So to provide a workaround the only thing i can think of right now is to call those "sub tests" directly.
<?php
class StackTest extends PHPUnit_Framework_TestCase {
/**
* #dataProvider provider
*/
public function testEmpty($data) {
$stack = array();
$this->assertTrue(empty($stack));
$this->nextTestOrJustAllInOneTestcaseSaidly($stack);
return $stack;
}
protected function nextTestOrJustAllInOneTestcaseSaidly($data) { ... }
Hope that helps or at least motivates someone else to answer ;)
I had the exact same problem with a test that depends on another test (and more specifically data returned by that test) which uses a data provider.
I overcame the problem by setting the value (which I would of normally simply returned from the dependent test) as a static variable, which I was then able to access in my other tests later.
<?php
class StackTest extends PHPUnit_Framework_TestCase {
protected static $foo;
public function provider() { ... }
/**
* #dataProvider provider
*/
public function testOne( $data ) {
self::$foo = array();
$this->assertTrue( empty( self::$foo ) );
}
/**
* #depends testOne
*/
public function testTwo() {
$this->assertTrue( empty( self::$foo ) );
}
Still hacky, but slightly less than perhaps calling the next test function from within another test.
If a test has a data provider, then all data provided will be fed into this test. Only then the next test is run, which might depend on the first test to succeed. What is NOT done is that all data provided to the first test is also fed to the second.
#dataProvider takes priority over #depends. A test can depend on another test that completely testes all of its data provided, but it won't get this data for itself. To get such a thing you really have to combine all dependant tests into one function.
On the other hand, such a test setup might be non-obvious in the first place, but tests should be easy to understand. Consider refactoring the tests.

Categories