Consider
class Foo {
public $att;
public function __construct ( $a ) { $this->att = $a; }
}
class Some {
public function callMe ( Foo $f ) {}
}
// class I want to test
class SuT {
public function testMe ( Some $s ) {
echo $s->callMe( new Foo('hi') );
}
}
I want to check whether Sut::testMe()
properly invokes Some::callMe()
. Since the parameter is a (Foo
) object (not a scalar type), I can't figure out how to call PHPUnit's with()
to run assertions on it. There is an assertAttributeEquals
method for example, but how do I feed it the invocation's argument?
What I'd like to do is this:
class SuTTest extends PHPUnit_Framework_TestCase {
public function testSuT () {
$stub = $this->getMock( 'Some' );
$stub->expects( $this->once() )->method( 'callMe' )
->with( $this->assertAttributeEquals('hi', 'att', $this->argument(0) );
/*
* Will $stub->callMe be called with a Foo object whose $att is 'hi'?
*/
$sut = new SuT();
$sut->testMe( $stub );
}
}
You just pass the expected values to the "with" method.
->with(1, $object, "paramThree");
you can also pass in a range of phpUnit assertions instead of the parameters (it defaults to equal to)
->with(1, $this->equalTo($object), "paramThree");
so for objects you would use $this->isInstanceOf("stdClass")
as a parameter to ->with
For the list of possible assertions look into: PHPUnit/Framework/Assert.php
for functions that return a new PHPUnit_Framework_Constraint
The first testcase just matches 2 arguments and works
The second one matches two and fails on argument 2
The last one tests that the passed in object is of type stdClass
<?php
class MockMe {
public function bla() {
}
}
class Demo {
public function foo(MockMe $x) {
$x->bla(1, 2);
}
public function bar(MockMe $x) {
$x->bla(1, new stdClass());
}
}
class DemoTest extends PHPUnit_Framework_TestCase {
public function testWorks() {
$x = new Demo();
$mock = $this->getMock("MockMe");
$mock->expects($this->once())->method("bla")->with(1,2);
$x->foo($mock);
}
public function testFails() {
$x = new Demo();
$mock = $this->getMock("MockMe");
$mock->expects($this->once())->method("bla")->with(1,3);
$x->foo($mock);
}
public function testObject() {
$x = new Demo();
$mock = $this->getMock("MockMe");
$mock->expects($this->once())->method("bla")->with(1, $this->isInstanceOf("stdClass"));
$x->bar($mock);
}
}
phpunit DemoTest.php
PHPUnit 3.5.13 by Sebastian Bergmann.
.F.
Time: 0 seconds, Memory: 4.25Mb
There was 1 failure:
1) DemoTest::testFails
Failed asserting that <integer:2> matches expected <integer:3>.
...DemoTest.php:12
...DemoTest.php:34
FAILURES!
Tests: 3, Assertions: 2, Failures: 1.
Even though the question is already answered fully (how to assert attribute of object passed as argument to mock) I think it's worth noting that PHPUnit supports a callback constraint to be passed to with().
I kept bumping with this thread while trying to find out how to run further assertions on the mocked object arguments. For instance, I needed to check the return value of some method. Obviously, and for sanity sake, there is no methodReturnValueEqualTo() equivalent to the attribute assertion used in the answer above.
Fortunately, PHPUnit does support (as of 3.7 at least) a callback constraint, which makes a lot of sense and is something you can find in specialised Mock libraries like Mockery.
Current PHPUnit version docs state the following:
The callback() constraint can be used for more complex argument verification. This constraint takes a PHP callback as its only argument. The PHP callback will receive the argument to be verified as its only argument and should return TRUE if the argument passes verification and FALSE otherwise.
Therefore, using the callback constraint, the OP example can now be expressed as:
class SuTTest extends PHPUnit_Framework_TestCase {
public function testSuT () {
$stub = $this->getMock('Some');
$stub->expects($this->once())
->method('callMe')
->with($this->callback(function($arg) {
return ($arg instanceof Some) && ($arg->att === 'hi');
})
);
/*
* Will $stub->callMe be called with a Foo object whose $att is 'hi'?
*/
$sut = new SuT();
$sut->testMe($stub);
}
}
And a test fail would look something like:
1) SuTTest::testSuT
Expectation failed for method name is equal to <string:callMe> when invoked 1 time(s)
Parameter 0 for invocation Some::callMe(Foo Object (...)) does not match expected value.
Failed asserting that Foo Object () is accepted by specified callback.
You can now run any logic on that argument.
Even better, despite not being a documented feature in PHPUnit docs, you can actually use assertions and get the benefits of assertion error messages:
class SuTTest extends PHPUnit_Framework_TestCase {
public function testSuT () {
// alias to circumvent php closure lexical variable restriction
$test = $this;
// proceed as normal
$stub = $this->getMock('Some');
$stub->expects($this->once())
->method('callMe')
// inject the test case in the closure
->with($this->callback(function($arg) use ($test) {
// use test assertions
$test->assertInstanceOf('Some', $arg);
$test->assertAttributeEquals('hi', 'att', $arg);
// return true to satisfy constraint if all assertions passed
return true;
})
);
/*
* Will $stub->callMe be called with a Foo object whose $att is 'hi'?
*/
$sut = new SuT();
$sut->testMe( $stub );
}
}
Don't really know how future proof is this strategy of using assertions and returning true. It is not documented and there is a tradeoff in the error message. You no longer get the parameter constraint message, so if you set assertions on multiple arguments you will have to infer, if possible, which one failed. But you do get a better description of the failed assertion inside the closure.
1) SuTTest::testSuT
Expectation failed for method name is equal to <string:callMe> when invoked 1 time(s)
Failed asserting that two strings are equal.
--- Expected
+++ Actual
@@ @@
-'hi'
+'no'
Hope this helps anyone facing a similar problem.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With