#dataProvider or #depends can I use it? - php

I have a class (called FormFilters), that class calls its methods within one method, in this case getProject.
class FormFilters extends KernelTestCase
{
public function getProject($filters)
{
$this->filters = $filters;
$this->getWhere($this->filters);
}
public function getWhere()
{
if ($this->filters->isValid()) {
$this->sql = $this->filterName($this->filters->get('name')->getData());
}
}
public function filterName()
{
//....
}
}
This is getProject method test:
public function test_getProject()
{
$formInterface = $this->createMock('Symfony\Component\Form\FormInterface');
$formInterface
->expects($this->at(0))
->method('isValid')
->willReturn(true); // come into conditional
$formInterface
->expects($this->at(1))
->method('get')
->with('name')
->will($this->returnSelf());
$formInterface
->expects($this->at(2))
->method('getData')
->will('data example');
$formFilters = new FormFilters();
$formFilters->getProject($formInterface); // my mock
}
So far all right. Now, I want to test getWhere method, I could do it independently, but if getProject has the same test (called to getWhere method), could I use the annotations #dataProvider or #depends, like this (example) :
/**
* #depends or/and #dataProvider test_getProject
*/
public function test_getWhere($dataToDepends)
{
// ... test ready !
}
It's possible ?

In your current set-up, positive case for getWhere() is already tested (in scope of test_getProject()). So, what is left to test in getWhere() is a negative case, when interpreter does not go inside of IF.
Test could be:
public function test_getWhere_invalid_filters()
{
$formInterface->expects($this->once())
->method('isValid')
->willReturn(false);
$formInterface->expects($this->never())
->method('get');
$formInterface->expects($this->never())
->method('getData');
$formFilters = new FormFilters();
//todo: inject $formInterface into $formFilterssomehow at this line.
$formFilters->getWhere();
}
Regarding your question with #depends - it's usually used when second test can not be executed before first is done. For example, first case creates some entity in database, and second test tries to delete entity, created in previous test. Another example - a static property of class, set in one test and expected to be read in another test. Generally speaking, having dependent tests, as well as dependent code units is not encouraged. And anyway, it's not your case, not what you need for the test.
Regarding #dataProvider - it's pretty usefull annotation. It allows to separate logic of the test from tested data. And also it allows to re-use same test with different data sets. Test, posted above, with #dataProvider will look like:
/**
* #dataProvider getWhere_invalid_filters_data_provider
*/
public function test_getWhere_invalid_filters($isValid, $getCallsCount, $getDataCallsCount)
{
$formInterface->expects($this->once())
->method('isValid')
->willReturn($isValid);
$formInterface->expects($this->exactly($getCallsCount))
->method('get');
$formInterface->expects($this->exactly($getDataCallsCount))
->method('getData');
$formFilters = new FormFilters();
//todo: inject $formInterface into $formFilterssomehow at this line.
$formFilters->getWhere();
}
public function getWhere_invalid_filters_data_provider()
{
return [
'case 1' => [
'isValid' => false,
'getCallsCount' => 0,
'getDataCallsCount' => 0,
],
];
}

Related

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

Mockery/Etsy PHPExtensions does not fail test if required methods are not called

I have the below code, which I would expect to fail when run as the class DoesNothing doesn't use the mock class or call any of the required methods on it.
<?php
class DoesNothing
{
}
class DoesNothingTest extends YourMockeryTestCase
{
/**
* #test
*/
public function somethingIsCalled()
{
$this->mock = Mockery::mock();
$keys = array(
'1234',
'abcxyz',
'*&(%&^$-*/~#:{}',
')*&GA^FAUIB(*',
'',
' ',
);
foreach ($keys as $key) {
$this->mock
->shouldReceive('remove')
->atLeast()->times(1)
->with($key);
}
$var = new DoesNothing($this->mock);
}
}
But when I run it, it passes. I would expect it to say "method remove was not called" etc.
What could be wrong? Something to do with how Mockery Talks to PHPUnit?
Thanks,
Martin
Edit:
I shoudl also mention we are using Etsy's PHPExtensions to integrate it into PHPUnit
Your method name should start with test, otherwise PHPUnit will not determine it as test.
public function testSomethingIsCalled()
edit
You have to call Mockery::close() in your teardown method for expectations to be executed. i.e.
/**
* Tear down
*/
public function tearDown()
{
\Mockery::close();
}

Combining dependencies with data providers

I have one test method that depends on another method that itself uses a data provider in PHPUnit:
/**
* #dataProvider getFields
*/
public function testCanDoSomeStuff($parm1, $parm2) {
$result = my_func($parm1, $parm2);
$this->assertNotNull($result);
return $result;
}
/**
* #depends testCanDoSomeStuff
*/
public function testCanDoSomeMoreStuff($result) {
$this->assertNotNull($result);
}
I also have a getFields() data provider function, no need to show that here.
The first test that relies on the data provider passes - $result is NOT null.
I expect that the result of the test will be passed to the dependent test as the $result parameter. However, the testCanDoSomeMoreStuff function receives a NULL parameter and the test fails.
Update
This simple test case demonstrates the problem:
class MyTest extends PHPUnit_Framework_TestCase {
/**
* #dataProvider myFunc
*/
public function testCanDoSomeStuff($value) {
$this->assertNotNull($value);
return $value;
}
/**
* #depends testCanDoSomeStuff
*/
public function testCanDoSomeMoreStuff($value) {
$this->assertNotNull($value);
}
/**
* Data provider function
*/
public function myFunc() {
$values = array('22');
return array($values);
}
}
As a workaround for now, I've stored the result in a static property between tests.
The problem is the result of several factors:
Each test result is stored in an array using the test's name as the key.
The name for a test that receives data is <name> with data set #<x>.
The #depends annotation doesn't accept multiple words.
There is a hacky workaround: override TestCase::getDataSetAsString to return a name that the annotation will accept. This is made slightly problematic since the required TestCase fields are private, but with PHP 5.3.2+ you can get around that.
Important: Unfortunately, you cannot have the dependent test run for every data row--only one specific row. If your data provider returns only one row of data, this isn't an issue.
Here's the code with a sample test. Note that you don't have to name your data row. If you leave off the 'foo' key, change the #depends to testOne-0.
class DependencyTest extends PHPUnit_Framework_TestCase
{
/**
* #dataProvider data
*/
public function testOne($x, $y) {
return $x + $y;
}
public function data() {
return array(
'foo' => array(1, 2),
);
}
/**
* #depends testOne-foo
*/
public function testTwo($z) {
self::assertEquals(3, $z);
}
protected function getDataSetAsString($includeData = false) {
if (!$includeData && $this->getPrivateField('data')) {
return '-' . $this->getPrivateField('dataName');
}
return parent::getDataSetAsString($includeData);
}
private function getPrivateField($name) {
$reflector = new ReflectionProperty('PHPUnit_Framework_TestCase', $name);
$reflector->setAccessible(true);
return $reflector->getValue($this);
}
}
Obviously, this is not a long-term solution. It would be better of you could have the dependent test run once for each test result from the method receiving the data. You could submit a feature request or pull request to PHPUnit.
If your $result in testCanDoSomeStuff() is really not null, then this should work.
To take this apart, first try to simplify it without the data provider, something like this:
class StackTest extends PHPUnit_Framework_TestCase {
public function testCanDoSomeStuff() {
$result = true;
$this->assertTrue($result);
return $result;
}
/**
* #depends testCanDoSomeStuff
*/
public function testCanDoSomeMoreStuff($result) {
$this->assertNotNull($result);
}
}
Testing this should result into something like...
~>phpunit test.php
PHPUnit 3.6.11 by Sebastian Bergmann.
..
Time: 1 second, Memory: 3.25Mb
OK (2 tests, 2 assertions)
Now add the data provider, replace my simple variable with your function and then test it again.
If this result differs, var_dump the variable $result before you return it in testcase testCanDoSomeStuff(). If it isn't null there, bug the behaviour.
I also expected the problem described to work, and after some research, I found out that this is not a bug, but an expected, not documented, behavior. The dependent test does not know about the data sets returned by the provider, and that's why the test parameter is null.
Source: https://github.com/sebastianbergmann/phpunit/issues/183#issuecomment-816066
The #dataProvider annotations get computed before test execution. Basically, the pre-test phase creates a test method for every set of parameters provided by the data provider. The #depends is dependent upon what is essentially the prototype of the data driven test, so in a way the #depends is on a non-existent (not executed test).
Another way to think of it, is that if provider was supplying more than one set of parameters. PHPUnit would make that many testDataProvider methods but there would not be that many testDataReceiver methods because there is not an #dataProvider method on that test method for the pre-test phase.
You can however had #depends and #dataProvider on the same test method. Just be careful to get the parameter order right, although in this case there may not be a first parameter.
Basically, you should use data providers when the data set has multiple rows. However, you can always use #depend and #dataProvider at the same time to achieve roughly the same behavior.

phpunit mock method multiple calls with different arguments

Is there any way to define different mock-expects for different input arguments? For example, I have database layer class called DB. This class has method called "Query ( string $query )", that method takes an SQL query string on input. Can I create mock for this class (DB) and set different return values for different Query method calls that depends on input query string?
It's not ideal to use at() if you can avoid it because as their docs claim
The $index parameter for the at() matcher refers to the index, starting at zero, in all method invocations for a given mock object. Exercise caution when using this matcher as it can lead to brittle tests which are too closely tied to specific implementation details.
Since 4.1 you can use withConsecutive eg.
$mock->expects($this->exactly(2))
->method('set')
->withConsecutive(
[$this->equalTo('foo'), $this->greaterThan(0)],
[$this->equalTo('bar'), $this->greaterThan(0)]
);
If you want to make it return on consecutive calls:
$mock->method('set')
->withConsecutive([$argA1, $argA2], [$argB1], [$argC1, $argC2])
->willReturnOnConsecutiveCalls($retValueA, $retValueB, $retValueC);
The PHPUnit Mocking library (by default) determines whether an expectation matches based solely on the matcher passed to expects parameter and the constraint passed to method. Because of this, two expect calls that only differ in the arguments passed to with will fail because both will match but only one will verify as having the expected behavior. See the reproduction case after the actual working example.
For you problem you need to use ->at() or ->will($this->returnCallback( as outlined in another question on the subject.
Example:
<?php
class DB {
public function Query($sSql) {
return "";
}
}
class fooTest extends PHPUnit_Framework_TestCase {
public function testMock() {
$mock = $this->getMock('DB', array('Query'));
$mock
->expects($this->exactly(2))
->method('Query')
->with($this->logicalOr(
$this->equalTo('select * from roles'),
$this->equalTo('select * from users')
))
->will($this->returnCallback(array($this, 'myCallback')));
var_dump($mock->Query("select * from users"));
var_dump($mock->Query("select * from roles"));
}
public function myCallback($foo) {
return "Called back: $foo";
}
}
Reproduces:
phpunit foo.php
PHPUnit 3.5.13 by Sebastian Bergmann.
string(32) "Called back: select * from users"
string(32) "Called back: select * from roles"
.
Time: 0 seconds, Memory: 4.25Mb
OK (1 test, 1 assertion)
Reproduce why two ->with() calls don't work:
<?php
class DB {
public function Query($sSql) {
return "";
}
}
class fooTest extends PHPUnit_Framework_TestCase {
public function testMock() {
$mock = $this->getMock('DB', array('Query'));
$mock
->expects($this->once())
->method('Query')
->with($this->equalTo('select * from users'))
->will($this->returnValue(array('fred', 'wilma', 'barney')));
$mock
->expects($this->once())
->method('Query')
->with($this->equalTo('select * from roles'))
->will($this->returnValue(array('admin', 'user')));
var_dump($mock->Query("select * from users"));
var_dump($mock->Query("select * from roles"));
}
}
Results in
phpunit foo.php
PHPUnit 3.5.13 by Sebastian Bergmann.
F
Time: 0 seconds, Memory: 4.25Mb
There was 1 failure:
1) fooTest::testMock
Failed asserting that two strings are equal.
--- Expected
+++ Actual
## ##
-select * from roles
+select * from users
/home/.../foo.php:27
FAILURES!
Tests: 1, Assertions: 0, Failures: 1
From what I've found, the best way to solve this problem is by using PHPUnit's value-map functionality.
Example from PHPUnit's documentation:
class SomeClass {
public function doSomething() {}
}
class StubTest extends \PHPUnit_Framework_TestCase {
public function testReturnValueMapStub() {
$mock = $this->getMock('SomeClass');
// Create a map of arguments to return values.
$map = array(
array('a', 'b', 'd'),
array('e', 'f', 'h')
);
// Configure the mock.
$mock->expects($this->any())
->method('doSomething')
->will($this->returnValueMap($map));
// $mock->doSomething() returns different values depending on
// the provided arguments.
$this->assertEquals('d', $stub->doSomething('a', 'b'));
$this->assertEquals('h', $stub->doSomething('e', 'f'));
}
}
This test passes. As you can see:
when the function is called with parameters "a" and "b", "d" is returned
when the function is called with parameters "e" and "f", "h" is returned
From what I can tell, this feature was introduced in PHPUnit 3.6, so it's "old" enough that it can be safely used on pretty much any development or staging environments and with any continuous integration tool.
It seems Mockery (https://github.com/padraic/mockery) supports this. In my case I want to check that 2 indices are created on a database:
Mockery, works:
use Mockery as m;
//...
$coll = m::mock(MongoCollection::class);
$db = m::mock(MongoDB::class);
$db->shouldReceive('selectCollection')->withAnyArgs()->times(1)->andReturn($coll);
$coll->shouldReceive('createIndex')->times(1)->with(['foo' => true]);
$coll->shouldReceive('createIndex')->times(1)->with(['bar' => true], ['unique' => true]);
new MyCollection($db);
PHPUnit, this fails:
$coll = $this->getMockBuilder(MongoCollection::class)->disableOriginalConstructor()->getMock();
$db = $this->getMockBuilder(MongoDB::class)->disableOriginalConstructor()->getMock();
$db->expects($this->once())->method('selectCollection')->with($this->anything())->willReturn($coll);
$coll->expects($this->atLeastOnce())->method('createIndex')->with(['foo' => true]);
$coll->expects($this->atLeastOnce())->method('createIndex')->with(['bar' => true], ['unique' => true]);
new MyCollection($db);
Mockery also has a nicer syntax IMHO. It appears to be a tad slower than PHPUnits built-in mocking capability, but YMMV.
Intro
Okay I see there is one solution provided for Mockery, so as I don't like Mockery, I am going to give you a Prophecy alternative but I would suggest you first to read about the difference between Mockery and Prophecy first.
Long story short: "Prophecy uses approach called message binding - it means that behaviour of the method does not change over time, but rather is changed by the other method."
Real world problematic code to cover
class Processor
{
/**
* #var MutatorResolver
*/
private $mutatorResolver;
/**
* #var ChunksStorage
*/
private $chunksStorage;
/**
* #param MutatorResolver $mutatorResolver
* #param ChunksStorage $chunksStorage
*/
public function __construct(MutatorResolver $mutatorResolver, ChunksStorage $chunksStorage)
{
$this->mutatorResolver = $mutatorResolver;
$this->chunksStorage = $chunksStorage;
}
/**
* #param Chunk $chunk
*
* #return bool
*/
public function process(Chunk $chunk): bool
{
$mutator = $this->mutatorResolver->resolve($chunk);
try {
$chunk->processingInProgress();
$this->chunksStorage->updateChunk($chunk);
$mutator->mutate($chunk);
$chunk->processingAccepted();
$this->chunksStorage->updateChunk($chunk);
}
catch (UnableToMutateChunkException $exception) {
$chunk->processingRejected();
$this->chunksStorage->updateChunk($chunk);
// Log the exception, maybe together with Chunk insert them into PostProcessing Queue
}
return false;
}
}
PhpUnit Prophecy solution
class ProcessorTest extends ChunkTestCase
{
/**
* #var Processor
*/
private $processor;
/**
* #var MutatorResolver|ObjectProphecy
*/
private $mutatorResolverProphecy;
/**
* #var ChunksStorage|ObjectProphecy
*/
private $chunkStorage;
public function setUp()
{
$this->mutatorResolverProphecy = $this->prophesize(MutatorResolver::class);
$this->chunkStorage = $this->prophesize(ChunksStorage::class);
$this->processor = new Processor(
$this->mutatorResolverProphecy->reveal(),
$this->chunkStorage->reveal()
);
}
public function testProcessShouldPersistChunkInCorrectStatusBeforeAndAfterTheMutateOperation()
{
$self = $this;
// Chunk is always passed with ACK_BY_QUEUE status to process()
$chunk = $this->createChunk();
$chunk->ackByQueue();
$campaignMutatorMock = $self->prophesize(CampaignMutator::class);
$campaignMutatorMock
->mutate($chunk)
->shouldBeCalled();
$this->mutatorResolverProphecy
->resolve($chunk)
->shouldBeCalled()
->willReturn($campaignMutatorMock->reveal());
$this->chunkStorage
->updateChunk($chunk)
->shouldBeCalled()
->will(
function($args) use ($self) {
$chunk = $args[0];
$self->assertTrue($chunk->status() === Chunk::STATUS_PROCESSING_IN_PROGRESS);
$self->chunkStorage
->updateChunk($chunk)
->shouldBeCalled()
->will(
function($args) use ($self) {
$chunk = $args[0];
$self->assertTrue($chunk->status() === Chunk::STATUS_PROCESSING_UPLOAD_ACCEPTED);
return true;
}
);
return true;
}
);
$this->processor->process($chunk);
}
}
Summary
Once again, Prophecy is more awesome! My trick is to leverage the messaging binding nature of Prophecy and even though it sadly looks like a typical, callback javascript hell code, starting with $self = $this; as you very rarely have to write unit tests like this I think it's a nice solution and it's definitely easy to follow, debug, as it actually describes the program execution.
BTW: There is a second alternative but requires changing the code we are testing. We could wrap the troublemakers and move them to a separate class:
$chunk->processingInProgress();
$this->chunksStorage->updateChunk($chunk);
could be wrapped as:
$processorChunkStorage->persistChunkToInProgress($chunk);
and that's it but as I didn't want to create another class for it, I prefer the first one.

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