I have a class containing four unit tests. The class looks like this:
class TestWorkflowService extends TestCase
{
private $containerMock;
private $workflowEntityMock;
private $workflowService;
public function setup()
{
$this->containerMock = $this->createMock(ContainerInterface::class);
$this->workflowService = new WorkflowService($this->containerMock);
$this->workflowEntityMock = $this->createMock(WorkflowInterface::class);
}
public function testGetWorkflowProcessByEntityNullResult()
{
self::assertNull($this->workflowService->getWorkflowProcessByEntity($this->workflowEntityMock));
}
public function testGetProcessHandlerByEntityNullResult()
{
self::assertNull($this->workflowService->getProcessHandlerByEntity($this->workflowEntityMock));
}
public function testRestartWorkflow()
{
$modelStateMock = $this->createMock(ModelState::class);
$processHandlerMock = $this->createMock(ProcessHandler::class);
$processHandlerMock->method('start')->willReturn($modelStateMock);
$this->containerMock->method('get')->willReturn($processHandlerMock);
self::assertNull($this->workflowService->restartWorkflow($this->workflowEntityMock));
}
public function setEntityToNextWorkflowState()
{
$modelStateMock = $this->createMock(ModelState::class);
$processHandlerMock = $this->createMock(ProcessHandler::class);
$processHandlerMock->method('start')->willReturn($modelStateMock);
$this->containerMock->method('get')->willReturn($processHandlerMock);
self::assertNull($this->workflowService->setEntityToNextWorkflowState($this->workflowEntityMock));
}
}
... but when I run PHPUnit, I get this result:
... 3
/ 3 (100%)
Time: 2.17 seconds, Memory: 5.75MB
OK (3 tests, 3 assertions)
Why is my fourth test not being recognized?
PHPUnit identifies test methods using the following rules:
The tests are public methods that are named test*.
Alternatively, you can use the #test annotation in a method's docblock to mark it as a test method.
This is so you can have other public methods in your test class without them being interpreted as tests (although I'm not sure why you'd ever actually do this).
Change your method name to testSetEntityToNextWorkflowState, or tag it with the #test annotation.
Related
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'));
}
}
I am trying to learn how to write tests using phpunit.
In the test case i am trying to write i am testing 3 methods. Test is if method 1 returns false then call method 2 and 3 else just stop.
class MyTest {
$mock1->getMock('some class1')
$mock1->expect($this->once())
->method('method1')
->will($this->returnValue(false));
$mock2->getMock('some_class2')
$mock2->expect($this->once())
->method('method2')
$mock2->method2($arg)
$mock2->expect($this->once())
->method('method3')
$mock3->method3($arg)
}
how do i test if method 2 and 3 were called.
Currently i am getting a failure that No Test was Found
Look at the documentation, roughly this is how your class should look like:
class MyTest extends PHPUnit_Framework_TestCase
{
public function setUp()
{
//initialize objects for each method to work with
}
public function testFeature()
{
$arg = 'something';
$mock1->getMock('some class1');
$mock1->expect($this->once())
->method('method1')
->will($this->returnValue(false));
$mock2->getMock('some_class2');
$mock2->expect($this->once())
->method('method2');
$mock2->method2($arg);
$mock2->expect($this->once())
->method('method3');
//$mock3->method3($arg);
}
}
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
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();
}
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.