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.
Related
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,
],
];
}
I have an interesting scenario in that I need a function to be defined in order to make tests for another function. The function I want to test looks something like this:
if (function_exists('foo') && ! function_exists('baz')) {
/**
* Baz function
*
* #param integer $n
* #return integer
*/
function baz($n)
{
return foo() + $n;
}
}
The reason I am checking for the existence of foo is because it may or may not be defined in a developer's project and the function baz relies on foo. Because of this, I only want baz to be defined if it can call foo.
The only problem is that so far it has been impossible to write tests for. I tried creating a bootstrap script in the PHPUnit configuration that would define a fake foo function and then require the Composer autoloader, but my main script still thinks foo is not defined. foo is not a Composer package and can not otherwise be required by my project. Obviously Mockery will not work for this either. My question is if anyone more experienced with PHPUnit has come across this issue and found a solution.
Thanks!
Start with a slight refactor of the code to make it more testable.
function conditionalDefine($baseFunctionName, $defineFunctionName)
{
if(function_exists($baseFunctionName) && ! function_exists($defineFunctionName))
{
eval("function $defineFunctionName(\$n) { return $baseFunctionName() + \$n; }");
}
}
Then just call it like this:
conditionalDefine('foo', 'bar');
Your PHPUnit test class will contain the following tests:
public function testFunctionIsDefined()
{
$baseName = $this->mockBaseFunction(3);
$expectedName = uniqid('testDefinedFunc');
conditionalDefine($baseName, $expectedName);
$this->assertTrue(function_exists($expectedName));
$this->assertEquals(5, $expectedName(2));
}
public function testFunctionIsNotDefinedBecauseItExists()
{
$baseName = $this->mockBaseFunction(3);
$expectedName = $this->mockBaseFunction($value = 'predefined');
conditionalDefine($base, $expectedName);
// these are optional, you can't override a func in PHP
// so all that is necessary is a call to conditionalDefine and if it doesn't
// error, you're in the clear
$this->assertTrue(function_exists($expectedName));
$this->assertEquals($value, $expectedName());
}
public function testFunctionIsNotDefinedBecauseBaseFunctionDoesNotExists()
{
$baseName = uniqid('testBaseFunc');
$expectedName = uniqid('testDefinedFunc');
conditionalDefine($base, $expectedName);
$this->assertFalse(function_exists($expectedName));
}
protected function mockBaseFunction($returnValue)
{
$name = uniqid('testBaseFunc');
eval("function $name() { return '$value'; }");
return $name;
}
That is sufficient for what you're asking. You can however, further refactor this code extracting the function generation into a code generator. That what you can write unit tests against the generator to make sure it creates the kind of function you expect.
This should work!
use MyProject\baz;
class YourTestCase
{
/** #var callable **/
protected $mockFoo;
/** #var callable **/
protected $fakeFoo;
public function setUp()
{
if (function_exists('foo')) {
$this->mockFoo = function($foosParams) {
foo($foosParams);
// Extra Stuff, as needed to make the test function right.
};
}
$this->fakeFoo = function($foosParams) {
// Completely mock out foo.
};
}
public function testBazWithRealFoo()
{
if (!$this->mockFoo) {
$this->markTestIncomplete('This system does not have the "\Foo" function.');
}
$actualResults = baz($n, $this->mockFoo);
$this->assertEquals('...', $actualResults);
}
public function testBazWithMyFoo()
{
$actualResults = baz($n, $this->fakeFoo);
$this->assertEquals('...', $actualResults);
}
}
Then modify your existing code:
if (function_exists('foo') && ! function_exists('baz')) {
/**
* Baz function
*
* #param integer $n
* #return integer
*/
function baz($n)
{
return foo() + $n;
}
}
namespace MyProject
{
function baz($bazParams, callable $foo = '\foo')
{
return $foo() + $bazParams;
}
}
Then instead of calling baz($n), you need to call:
use MyProject\baz;
baz($bazParams);
It's like Dependency Injection for functions, yo ;-)
Is this sufficient?
to-test.php:
<?php
if (function_exists('foo') && ! function_exists('baz')) {
/**
* Baz function
*
* #param integer $n
* #return integer
*/
function baz($n)
{
return foo() + $n;
}
}
BazTest.php:
<?php
class BazTest extends PHPUnit_Framework_TestCase {
public function setUp()
{
function foo()
{
// Appropriate mock goes here
return 1;
}
include __DIR__ . '/to-test.php';
}
public function testBaz()
{
$this->assertEquals(2, baz(1));
$this->assertEquals(3, baz(2));
}
}
Running the test yields:
PHPUnit 5.4.8 by Sebastian Bergmann and contributors.
. 1 / 1 (100%)
Time: 54 ms, Memory: 8.00MB
OK (1 test, 2 assertions)
It seems this is a situation where you need to mock visibility, which is definitely outside the realm of PHPUnit which a library of classes. So you can still use PHPUnit, but might have to step outside of it to achieve your goal.
The only way I can think of to create a mock of a function is right inside the test case, above the object extending \PHPUnit_Framework_TestCase.
function foo() {
return 1;
}
if (function_exists('foo') && ! function_exists('baz')) {
/**
* Baz function
*
* #param integer $n
* #return integer
*/
function baz($n)
{
return foo() + $n;
}
}
class TestBaz extends \PHPUnit_Framework_TestCase
{
public function test_it() {
$this->assertSame(4,baz(3));
}
}
You might have to create two files, one with foo and one without, or four if you want to test foo/not foo and baz/not baz. This is definitely an outside-the-box option, that I wouldn't normally recommend, but in your case, it might be your best bet.
Adding it in the bootstrap file doesn't work - why?
Is it because sometimes the function baz is (A) correctly created by the system and sometimes (B) you need to mock it? Or (C) do you always need to mock it?
Case A: Why is the code creating a vital function sporadically on the fly?
Case B: A function can only be registrered once, and never unregistered or overwritten. Therefore, you either go with the mock or you don't. No mixing is allowed.
Case C: If you always need to mock it, and you add it to the bootstrap file, it will be defined. Regarding what you've tried, either your bootstrap file for phpunit isn't loaded correctly or you misspelled the function's name.
I'm sure you've correctly configured your phpunit bootstrapping, but for good measure, does it look anything like the following:
/tests/phpunit.xml:
<phpunit
bootstrap="phpunit.bootstrap.php"
</phpunit>
/tests/phpunit.bootstrap.php:
<?php
require(__DIR__ . "/../bootstrap.php"); // Application startup logic; this is where the function "baz" gets defined, if it exists
if (function_exists('foo') && ! function_exists('baz')) {
/**
* Baz function
*
* #param integer $n
* #return integer
*/
function baz($n)
{
return foo() + $n;
}
}
Don't create the function baz on the fly in your tests, e.g. in a setUp function.
Test suites in phpunit use the same bootstrapper. Therefore, if you need to test cases where function baz is defined and other cases where it is not defined (and you need to mock it), you need to split up the tests folder, e.g. in two different folders, each with their phpunit.xml and phpunit.bootstrap.php files. E.g. /tests/with-baz and /tests/mock-baz. From these two folders, run the tests separately. Just create symlinks to the phpunit in each subfolder (e.g. from /test/with-baz create ln -s ../../vendor/bin/phpunit if composer is in the root) to ensure you run the same version of phpunit in both scenarios.
The ultimate solution is, of course, to figure out where the baz function is being defined and manually include the culprit script file, if at all possible, to ensure the correct logic is being applied.
Alternative
Use phpunit's #runInSeparateProcess annotation and define the function as needed.
<?php
class SomeTest extends \PHPUnit_Framework_TestCase
{
/**
* #runInSeparateProcess
*/
public function testOne()
{
if (false === function_exists('baz')) {
function baz() {
return 42;
}
}
$this->assertSame(42, baz());
}
public function testTwo()
{
$this->assertFalse(function_exists('baz'));
}
}
Code will explain everything:
<?php
class ATest extends PHPUnit_Framework_TestCase
{
public function testDestructorOnOriginalClass() {
$a = new A(); // It
unset($a); // works
echo " great!"; // great!
$this->expectOutputString('It works great!');
}
public function testDestructorOnMockedClass() {
$a = $this->getMock('A', array('someNonExistingMethod')); // It
unset($a); // works
echo " great!"; // great!
$this->expectOutputString('It works great!');
}
}
class A {
public function __construct()
{
echo "It";
}
public function __destruct()
{
echo " works";
}
}
and the output:
# phpunit ATest.php
PHPUnit 3.7.13 by Sebastian Bergmann.
.F
Time: 0 seconds, Memory: 3.50Mb
There was 1 failure:
1) ATest::testDestructorOnMockedClass
Failed asserting that two strings are equal.
--- Expected
+++ Actual
## ##
-'It works great!'
+'It great! works'
FAILURES!
Tests: 2, Assertions: 2, Failures: 1.
As you can see in the second test it prints works in wrong order, probably because phpunit stores reference to mock somewhere and __destruct() is called at the end of test... Ok I've already checked getMock() method and indeed it stores reference to mocked object ($this->mockObjects[] = $mockObject;) efficiently blocking object from being destructed and so __destructor() is never called.
/**
* Returns a mock object for the specified class.
*
* #param string $originalClassName
* #param array $methods
* #param array $arguments
* #param string $mockClassName
* #param boolean $callOriginalConstructor
* #param boolean $callOriginalClone
* #param boolean $callAutoload
* #param boolean $cloneArguments
* #return PHPUnit_Framework_MockObject_MockObject
* #throws PHPUnit_Framework_Exception
* #since Method available since Release 3.0.0
*/
public function getMock($originalClassName, $methods = array(), array $arguments = array(), $mockClassName = '', $callOriginalConstructor = TRUE, $callOriginalClone = TRUE, $callAutoload = TRUE, $cloneArguments = FALSE)
{
$mockObject = PHPUnit_Framework_MockObject_Generator::getMock(
$originalClassName,
$methods,
$arguments,
$mockClassName,
$callOriginalConstructor,
$callOriginalClone,
$callAutoload,
$cloneArguments
);
$this->mockObjects[] = $mockObject;
return $mockObject;
}
So the question is - is there a way to prevent this? Ignoring __destruct() when it should be called is I think bad limitation.
The thing is when you don't pass any values to a second parameter of "getMock" method, PHPUnit will stub all methods from the class you are mocking (including "__destruct").
But if you specify at least one method (it may be even non existing method) PHPUnit will stub only these methods you pass in second argument.
So if you want keep all methods, but you want also create mock, you should do this in that way:
$mock = $this->getMock('A', array('someNonExistingMethod'));
If you change this line you test should pass.
You're just unsetting the local variable - you are not destroying the object itself.
The object is also kept by PHPUnit itself, too. So there is still a reference around, thus your unset() does not lead to a __destruct().
So, the current behavior cannot be changed. Open a bug in the phpunit issue tracker.
If we clone the mock object and unset it, __destruct() of the cloned object can be called during the test. It may help you to test __destruct().
Please note that this method cannot prevent __destruct() of the original mock object to be called at the end of the test.
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.
I use Factories (see http://www.php.net/manual/en/language.oop5.patterns.php for the pattern) a lot to increase the testability of our code. A simple factory could look like this:
class Factory
{
public function getInstanceFor($type)
{
switch ($type) {
case 'foo':
return new Foo();
case 'bar':
return new Bar();
}
}
}
Here is a sample class using that factory:
class Sample
{
protected $_factory;
public function __construct(Factory $factory)
{
$this->_factory = $factory;
}
public function doSomething()
{
$foo = $this->_factory->getInstanceFor('foo');
$bar = $this->_factory->getInstanceFor('bar');
/* more stuff done here */
/* ... */
}
}
Now for proper unit testing I need to mock the object that will return stubs for the classes, and that is where I got stuck. I thought it would be possible to do it like this:
class SampleTest extends PHPUnit_Framework_TestCase
{
public function testAClassUsingObjectFactory()
{
$fooStub = $this->getMock('Foo');
$barStub = $this->getMock('Bar');
$factoryMock = $this->getMock('Factory');
$factoryMock->expects($this->any())
->method('getInstanceFor')
->with('foo')
->will($this->returnValue($fooStub));
$factoryMock->expects($this->any())
->method('getInstanceFor')
->with('bar')
->will($this->returnValue($barStub));
}
}
But when I run the test, this is what I get:
F
Time: 0 seconds, Memory: 5.25Mb
There was 1 failure:
1) SampleTest::testDoSomething
Failed asserting that two strings are equal.
--- Expected
+++ Actual
## ##
-bar
+foo
FAILURES!
Tests: 1, Assertions: 0, Failures: 1.
So obviously it is not possible to let a mock object return different values depending on the passed method arguments this way.
How can this be done?
The problem is that the PHPUnit Mocking doesn't allow you to do this:
$factoryMock->expects($this->any())
->method('getInstanceFor')
->with('foo')
->will($this->returnValue($fooStub));
$factoryMock->expects($this->any())
->method('getInstanceFor')
->with('bar')
->will($this->returnValue($barStub));
You can only have one expects per ->method();. It is not aware of the fact that the parameters to ->with() differ!
So you just overwrite the first ->expects() with the second one. It's how those assertions are implemented and it's not what one would expect. But there are workarounds.
You need to define one expects with both behaviors / return values!
See: Mock in PHPUnit - multiple configuration of the same method with different arguments
When adapting the example to your problem it could look like this:
$fooStub = $this->getMock('Foo');
$barStub = $this->getMock('Bar');
$factoryMock->expects($this->exactly(2))
->method('getInstanceFor')
->with($this->logicalOr(
$this->equalTo('foo'),
$this->equalTo('bar')
))
->will($this->returnCallback(
function($param) use ($fooStub, $barStub) {
if($param == 'foo') return $fooStub;
return $barStub;
}
));
Create a simple stub factory class whose constructor takes the instances it should return.
class StubFactory extends Factory
{
private $items;
public function __construct(array $items)
{
$this->items = $items;
}
public function getInstanceFor($type)
{
if (!isset($this->items[$type])) {
throw new InvalidArgumentException("Object for $type not found.");
}
return $this->items[$type];
}
}
You can reuse this class in any unit test.
class SampleTest extends PHPUnit_Framework_TestCase
{
public function testAClassUsingObjectFactory()
{
$fooStub = $this->getMock('Foo');
$barStub = $this->getMock('Bar');
$factory = new StubFactory(array(
'foo' => $fooStub,
'bar' => $barStub,
));
...no need to set expectations on $factory...
}
}
For completeness, if you don't mind writing brittle tests, you can use at($index) instead of any() in your original code. This will break if the system under test changes the order or number of calls to the factory, but it's easy to write.
$factoryMock->expects($this->at(0))
->method('getInstanceFor')
->with('foo')
->will($this->returnValue($fooStub));
$factoryMock->expects($this->at(1))
->method('getInstanceFor')
->with('bar')
->will($this->returnValue($barStub));
you should change your "business logic" ... i mean you don't have to pass Factory to the Sample constructor, you have to pass the exact parameters you need