Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Harnessing the Power of Mocks and Stubs in PHPU...

Harnessing the Power of Mocks and Stubs in PHPUnit / #laravellivejp

Avatar for asumikam

asumikam

May 25, 2026

More Decks by asumikam

Other Decks in Technology

Transcript

  1. Welcome to Japan! ೔ຊ΁Α͏ͦ͜ ೔ຊ΁Α͏ͦ͜ ೔ຊ΁Α͏ͦ͜ I am Asumi 🍊

    on X, phpc.sosial, and so on! Please follow me 🫶 SNS is @asumikam
  2. Why do we write tests? To improve quality! To reduce

    bugs! Because my senior told me to write it…
  3. With tests, we can: ✴ Express documentation / speci fi

    cation ✴ Ease code review ✴ Catch regressions early ✴ Improve design
  4. I talk about: First-half The difference between mocks and stubs

    (Test doubles, mocks, stubs, Anti-patterns,…)
  5. #1 The difference between mocks and stubs Mocks: Stubs: Assertion

    Imitation and Imitation The Difference Is
  6. l Unit Testing: Principles, Practices, and Patterns Chapter 5. Mocks

    and test fragility #1 The difference between mocks and stubs Mocks help to emulate and examine outcoming interactions. These interactions are calls the SUT makes to its dependencies to change their state. Mocks Behave As Imitations and Assertions
  7. l Unit Testing: Principles, Practices, and Patterns Chapter 5. Mocks

    and test fragility #1 The difference between mocks and stubs Stubs help to emulate incoming interactions. These interactions are calls the SUT makes to its dependencies to get input data. Stubs Behave as Imitations Only
  8. My answer is whether the dependency belongs to the speci

    fi cation. #1 The difference between mocks and stubs
  9. ✴ Test code must express the speci fi cation. ✴

    Reading tests should reveal the behavior without product code. ✴ I use real objects whenever possible. ✴ Prefer Classical style rather than London style ✴ But when they're slow or complex, I reach for Test Doubles. My Core Philosophy #1 The difference between mocks and stubs
  10. ✴ If yes, use mocks. ✴ You care about the

    behavior / side effect. ✴ Assert it happened. ✴ If no, use stubs. ✴ You just need the return value. ✴ The interaction itself doesn't matter. Do You Want to Assert It as Part of the Spec? #1 The difference between mocks and stubs
  11. When this goes right your test reads like a spec.

    When this goes wrong let's look at what happens. #1 The difference between mocks and stubs
  12. ✴ Used well, test doubles bring the spec into focus.

    ✴ Misused, they do the opposite. ✴ Overused mocks or stubs share one outcome: the test no longer expresses the spec. When Overused, Tests Get Worse #1 The difference between mocks and stubs
  13. ✴ Refactoring turns green to red — the behavior is

    fi ne, but the test breaks. ✴ The test locks in calls, args, and order — it knows the inside too well. ✴ It asserts "HOW it's done," not "WHAT it should do.” ✴ Good tests should survive any refactor, but a fragile test can’t. Overused Mocks = Fragile Tests #1 The difference between mocks and stubs
  14. ✴ Tests stay green — even when the real dependency

    quietly changes. ✴ The stub freezes the world at a speci fi c time. When reality drifts, the stub doesn't notice. ✴ Green here means "it ran." Not "it works.” ✴ Good tests should prove the behavior is correct, but a blank-check test can’t. Overused Stubs = Blank-Check Tests #1 The difference between mocks and stubs
  15. ✴ Overuse turns tests into a liability — not the

    asset they should be. ✴ The check: "Does this test express the spec I actually care about?" ✴ My rule: real objects by default. Test doubles only when they get in the way. Let the Spec Decide #1 The difference between mocks and stubs
  16. Tests Are Messages — to Your Teammates, to Your Future

    Self. #1 The difference between mocks and stubs
  17. ✴ When you're clear on what spec to convey, mocks

    and stubs really start to work for you. ✴ The right choice shifts with your design, your layers, your team. ✴ So make that judgment together. Tests Are Messages — to Your Teammates, to Your Future Self. #1 The difference between mocks and stubs
  18. Let's review how PHPUnit handles mocks and stubs. #2 Best

    Practices in the Latest Environment
  19. We Used to Build Everything with getMock() in PHPUnit 4.x

    This style was my fi rst experience of test doubles 😆 #2 Best Practices in the Latest Environment
  20. We Got createMock() at First in PHPUnit 5.x But we

    still used createMock as stubs. #2 Best Practices in the Latest Environment
  21. We Got createStub() at First in PHPUnit 8.x We can

    distinguish stubs from mocks! (But same internal behavior) #2 Best Practices in the Latest Environment
  22. 10 11 12 13 createStub() + expects() → silently ignored

    10.0.0 11.0.0 createStub() + expects() → [warning] hard deprecated 12.0.0 createStub() + expects() → [exception] removed 12.5.0 createMock() without expects() → [notice] added 12.5.5 any() → [soft deprecated] 13.0.0 any() → [hard deprecated] 13.0.2 with*() without expects() → [hard deprecated] 12.5.11 with*() on stubs → [soft deprecated] #2 Best Practices in the Latest Environment
  23. 10 11 12 13 createStub() + expects() → silently ignored

    10.0.0 11.0.0 createStub() + expects() → [warning] hard deprecated 12.0.0 createStub() + expects() → [exception] removed 12.5.0 createMock() without expects() → [notice] added 12.5.5 any() → [soft deprecated] 13.0.0 any() → [hard deprecated] 13.0.2 with*() without expects() → [hard deprecated] 12.5.11 with*() on stubs → [soft deprecated] #2 Best Practices in the Latest Environment
  24. 10 11 12 13 createStub() + expects() → silently ignored

    10.0.0 11.0.0 createStub() + expects() → [warning] hard deprecated 12.0.0 createStub() + expects() → [exception] removed 12.5.0 createMock() without expects() → [notice] added 12.5.5 any() → [soft deprecated] 13.0.0 any() → [hard deprecated] 13.0.2 with*() without expects() → [hard deprecated] 12.5.11 with*() on stubs → [soft deprecated] #2 Best Practices in the Latest Environment
  25. 10 11 12 13 createStub() + expects() → silently ignored

    10.0.0 11.0.0 createStub() + expects() → [warning] hard deprecated 12.0.0 createStub() + expects() → [exception] removed 12.5.0 createMock() without expects() → [notice] added 12.5.5 any() → [soft deprecated] 13.0.0 any() → [hard deprecated] 13.0.2 with*() without expects() → [hard deprecated] 12.5.11 with*() on stubs → [soft deprecated] #2 Best Practices in the Latest Environment
  26. The Basic Idea is Mocks Can Not Be Used as

    Stubs Stubs Can Not Be Used as Mocks #2 Best Practices in the Latest Environment
  27. Best Practices for Handling Mocks and Stubs in PHPUnit Are

    Evolving. #2 Best Practices in the Latest Environment
  28. ✴ PHPUnit is getting intentional about the distinction — not

    just naming it, but enforcing it at the API level ✴ That means version upgrades now come with a cost your old doubles need to express intent ✴ Let's walk through what that looks like in practice — through my own migration PHPUnit Has Been Drawing a Clearer Line #2 Best Practices in the Latest Environment
  29. Upgrade PHP to 8.5 and PHPUnit to 12.5.x #2 Best

    Practices in the Latest Environment
  30. Upgrade PHP to 8.5 and PHPUnit to 12.5.x Ran the

    tests — 615 PHPUnit Notices l No expectations were configured for the mock object for XXX.Consider refactoring your test code to use a test stub instead. The #[AllowMockObjectsWithoutExpectations] attribute can be used to opt out of this check. #2 Best Practices in the Latest Environment
  31. This test has all mocks properly con fi gured ✅

    #2 Best Practices in the Latest Environment
  32. This test has some mocks uncon fi gured 😣 #2

    Best Practices in the Latest Environment
  33. Every Mock Now Requires an Assertion ✴ This makes sense,

    but it completely breaks the way we used to write tests. ✴ A mock with no expects() = effectively a stub ✴ So it triggers the PHPUnit Notice ✴ the "." becomes an “N” #2 Best Practices in the Latest Environment
  34. Dealing With Mock Without Expectation Notices ✴ Situation: Shared setup()

    methods used for both mocking and stubbing ✴ Solution 1: Replace; use createStub() for assertion ✴ Solution 2: Opt-out using the attribute: #[AllowMockObjectsWithoutExpectations] How to deal with "No expectation were con fi gured for the mock object" https://github.com/sebastianbergmann/phpunit/issues/6437 Case 1 #2 Best Practices in the Latest Environment
  35. How to Ignore All Mock Without Expectation Notices at Once

    ✴ Situation: You want a global con fi guration to ignore all notices. ✴ Solution: There is no way to do that in PHPUnit. Deal with each notice one by one. Provider a new con fi guration option to ignore mock without expectations https://github.com/sebastianbergmann/phpunit/issues/6439 Case 2 #2 Best Practices in the Latest Environment
  36. Dealing With Mock Without Expectation Notices in Symfony ✴ Situation:

    The Symfony framework ended up with many notices in PHPUnit 12.5 ✴ Solution: Replace createMock() with createStub() in each module [Validator] ConstraintValidatorTestCase has PHPUnit notices with PHPUnit 12.5 https://github.com/symfony/symfony/issues/62669 Case 3 #2 Best Practices in the Latest Environment
  37. Dealing With Mock Without Expectation Notices in Drupal ✴ Situation:

    The Drupal CMS ended up with many notices in PHPUnit 12.5 ✴ Solution: Broken down to 37 subtasks, replace createMock() with createStub() in each module [meta] Refactor tests to use stubs instead of mocks where mocks do not con fi gure expectations https://www.drupal.org/project/drupal/issues/3561671 Case 4 #2 Best Practices in the Latest Environment
  38. Dealing With Mock Without Expectation Notices in CakePHP ✴ Situation:

    The CakePHP framework ended up with many notices in PHPUnit 12.5 ✴ Solution: Fixed each test similarly to other cases at fi rst. ✴ Then, deciding that the PHPUnit API is not as stable, and ended up using Mockery. https://github.com/cakephp/cakephp/pull/19196 https://github.com/cakephp/cakephp/pull/19259 Case 5 #2 Best Practices in the Latest Environment
  39. How About Laravel? ✴ Laravel's mocking helpers are built on

    Mockery, not PHPUnit’s native mocks. ✴ Since Mockery 1.0: ✴ $double->allows() = stub behavior ✴ $double->expects() = mock behavior ✴ PHPUnit is now enforcing what Mockery has exposed via API since 2017 #2 Best Practices in the Latest Environment
  40. What I Did: Back to Basics ✴ PHPUnit is highly

    motivated to draw a clear line between mocks and stubs. ✴ I asked myself — what makes a good test? ✴ A good test expresses the speci fi cation ✴ To do that, test doubles must express intent too ✴ So I committed to fi xing every Notice, step by step #2 Best Practices in the Latest Environment
  41. My Migration Steps (1)Bulk-add the opt-out attribute via a custom

    rector → PHP 8.5 done (2)Replace what rectorphp/rector-phpunit can replace (3)Peel that attribute off one-by-one (4)And then, on to PHPUnit 12.5.x 👏 #2 Best Practices in the Latest Environment
  42. rectorphp/rector-phpunit Auto-converts createMock → createStub ✴ It reduces the count

    and is worth trying. ✴ Sent a PR for intersection-type support — merged 😘 #2 Best Practices in the Latest Environment
  43. The any() Matcher Is Hard Deprecated from 13.0.0 Migration Point

    1 Scheduled to be removed in 14.0.0 #2 Best Practices in the Latest Environment
  44. with*() Without expects() Is Hard Deprecated from 13.0.2 Migration Point

    2 Scheduled to be removed in 14.0.0 #2 Best Practices in the Latest Environment
  45. atLeast() With a Non-Positive Argument Is Hard Deprecated from 13.0.2

    Migration Point 3 Scheduled to be removed in 14.0.0 #2 Best Practices in the Latest Environment
  46. ✴ My migration points are: ✴ the any() matcher is

    deprecated ✴ with*() without expects() is deprecated ✴ atLeast() with a non-positive argument is deprecated ✴ It closes off "ambiguous mocks" at the spec level ✴ “assert with createMock, imitate with createStub" — now enforced by the API And Now, We Are on PHPUnit 13.x.x #2 Best Practices in the Latest Environment
  47. When You Assert and Imitate: Use createMock() ✴ createMock() only

    when it is part of the spec ✴ Always pair with expects($this->once()) or exactly(n) ✴ Always pair with() with expects() ✴ Drop any() ✴ Drop atLeast() with non positive-arguments #2 Best Practices in the Latest Environment
  48. When You Imitate Only: Use createStub ✴ createStub() for dependencies

    you don't need to assert ✴ Con fi gure with method() + willReturn(), nothing else ✴ Never write expects() ✴ Never use with() #2 Best Practices in the Latest Environment
  49. Spec-Expressing Tests Become the "Guardrail" of the Al ✴ The

    clearer the line, the more valuable "spec-expressing tests" become ✴ If the spec lives in the test, Al has something to lean on for the next move ✴ Even when the spec changes, "what to protect" stays in the test ✴ An ambiguous test makes no guardrail #2 Best Practices in the Latest Environment
  50. If I need test double with imitation and assertion, I

    use mocks. If only imitation, I use stubs.