Flag of Ukraine
SymfonyCasts stands united with the people of Ukraine
This tutorial has a new version, check it out!

Mocks: expects() Assert Method is Called Correctly

Keep on Learning!

If you liked what you've learned so far, dive in!
Subscribe to get access to this tutorial plus
video, code and script downloads.

Start your All-Access Pass
Buy just this tutorial for $10.00

With a Subscription, click any sentence in the script to jump to that part of the video!

Login Subscribe

Go back to DinosaurFactory. Time to create another bug! Comment out the length line again. This time, do call the method... but instead of the spec, just pass foo. Whoops! We're calling the right method... but we've passed the wrong argument!

What happens to the tests?


Yep... they pass! If this possibility frightens you more than the sound of raptor claws on the door... We can also cover this in the test.

In addition to saying that getLengthFromSpecification() should return 20, you can also say that the method must be called exactly once or with some exact arguments. Then, if the method is not called once or is called with different arguments, the test will fail.

Move the method() call onto the next line. Then add ->expects($this->once()). The method must now be called exactly once.

... lines 1 - 9
class DinosaurFactoryTest extends TestCase
... lines 12 - 56
public function testItGrowsADinosaurFromSpecification(string $spec, bool $expectedIsCarnivorous)
... lines 61 - 67
... lines 69 - 78

Then, after method(), add ->with($spec). This with() function is pretty sweet: if the real method accepts three arguments, then you'll pass those three arguments to with(). In this case, if the value passed to the first argument of getLengthFromSpecification() does not match $spec, the test will fail.

... lines 1 - 9
class DinosaurFactoryTest extends TestCase
... lines 12 - 56
public function testItGrowsADinosaurFromSpecification(string $spec, bool $expectedIsCarnivorous)
... line 60
... lines 63 - 67
... lines 69 - 78

This should finally kill the test. Try it!


Yes! Failure because, for getLengthFromSpecification(), large herbivore does not match the actual value: foo.

Awesome! Move back to DinosaurFactory, and re-fix the length line. Double-check that this fixes things. It does!

... lines 1 - 7
class DinosaurFactory
... lines 10 - 21
public function growFromSpecification(string $specification): Dinosaur
... lines 24 - 25
$length = $this->lengthDeterminator->getLengthFromSpecification($specification);
... lines 27 - 35
... lines 37 - 45

This is really cool stuff. But you should not use it everywhere. Remember: at this point, we're really testing how the internal code of the method is written. The more we do this, the more often the tests will break when really... they shouldn't. Basically, we're micro-managing our own code!

For example, what if we refactor the method and decide to call getLengthFromSpecification() two times? The tests will break! Sure, our code might now be a little inefficient... but as long as we still create the correct Dinosaur, shouldn't the tests pass? You need to decide how strict each test should be.

Asserting Different Number of Method Calls

And if you don't care how many times a method is called, you can use $this->any() instead. Back on the Test Doubles documentation, search for once()... and find the next result until you see a list of "matchers".

These are classes, but there's a shortcut to use each, like $this->any(), $this->never(), $this->atLeastOnce() and $this->exactly().

Smarter Argument Asserts

You can also do a little bit of magic for the with() method. Go back to the docs and search for "anything". Cool! This is an example where the method should be called with three arguments. But instead of passing the exact values, we assert that the first argument is greater than zero, the second is a string that contains the word "Something" and we don't care at all what is passed as the third argument.

Search once more for callback(). This is the most powerful option: you create your own callback and do whatever logic you want to determine if the argument passed is valid.

We've got mocking down! But it's so important that I want to go through one more, fully-featured and classic example.

Leave a comment!

Login or Register to join the conversation
Mehul-J Avatar
Mehul-J Avatar Mehul-J | posted 1 year ago

It was nice if you would have showed the testing of eventsubscribers and eventlisteners


Hey Mehul-J!

That's actually a good point! I test these with 2 strategies:

1) Unit test: if the code is complex enough, I'll unit test the event listener method. But usually, if the logic is this complex, I'll isolate the main part of the logic to a separate class and unit test *that* class.

2) Functional test: Once your logic is unit tested (somewhere), all you really need to know is "is my event listener/subscriber" correctly configured so that it was executed? Depending on what your listener does, this can be tricky or not tricky. Basically, I would make a request (in a test) to my site in order to trigger the event listener, and then "assert" something that it did.

I noticed that you asked this in the "mocks" section, so I may not have answered exactly what you were asking - let me know if that's the case :). If I were unit testing an event listener/subscriber, I would mock any constructor dependencies it has... but for the event argument, I would typically try to create that directly, as those are usually simple objects. But if it's complicated enough, you could mock that too.


Default user avatar

Wrong code block after line **Awesome! Move back to DinosaurFactory, and re-fix the length line. Double-check that this fixes things. It does!**


@Matt Thanks for informing us! I just fixed that code block :)

Cat in space

"Houston: no signs of life"
Start the conversation!

While the fundamentals of PHPUnit haven't changed, this tutorial *is* built on an older version of Symfony and PHPUnit.

What PHP libraries does this tutorial use?

// composer.json
    "require": {
        "php": "^7.0, <7.4",
        "composer/package-versions-deprecated": "^1.11", // 1.11.99
        "doctrine/doctrine-bundle": "^1.6", // 1.10.3
        "doctrine/orm": "^2.5", // v2.7.2
        "incenteev/composer-parameter-handler": "^2.0", // v2.1.2
        "sensio/distribution-bundle": "^5.0.19", // v5.0.21
        "sensio/framework-extra-bundle": "^3.0.2", // v3.0.28
        "symfony/monolog-bundle": "^3.1.0", // v3.1.2
        "symfony/polyfill-apcu": "^1.0", // v1.6.0
        "symfony/swiftmailer-bundle": "^2.3.10", // v2.6.7
        "symfony/symfony": "3.3.*", // v3.3.13
        "twig/twig": "^1.0||^2.0" // v2.4.4
    "require-dev": {
        "doctrine/data-fixtures": "^1.3", // 1.3.3
        "doctrine/doctrine-fixtures-bundle": "^2.3", // v2.4.1
        "liip/functional-test-bundle": "^1.8", // 1.8.0
        "phpunit/phpunit": "^6.3", // 6.5.2
        "sensio/generator-bundle": "^3.0", // v3.1.6
        "symfony/phpunit-bridge": "^3.0" // v3.4.30