Slide 1

Slide 1 text

asumikam Harnessing the Power of Mocks and Stubs in PHPUnit

Slide 2

Slide 2 text

͓͸Α͏ʙ Good Morning Everyone!

Slide 3

Slide 3 text

Welcome to Japan! ೔ຊ΁Α͏ͦ͜ ೔ຊ΁Α͏ͦ͜ ೔ຊ΁Α͏ͦ͜ I am Asumi 🍊 on X, phpc.sosial, and so on! Please follow me 🫶 SNS is @asumikam

Slide 4

Slide 4 text

belong to Linkage, Community Sponsor

Slide 5

Slide 5 text

am The Organizer of PHP Conference Odawara Next: April 10, 2027

Slide 6

Slide 6 text

I usually live as a Ninja in Odawara 🥷🏯🍥

Slide 7

Slide 7 text

Anyway

Slide 8

Slide 8 text

My talk is about

Slide 9

Slide 9 text

Question:

Slide 10

Slide 10 text

Why do we write tests?

Slide 11

Slide 11 text

Why do we write tests? To improve quality!

Slide 12

Slide 12 text

Why do we write tests? To improve quality! To reduce bugs!

Slide 13

Slide 13 text

Why do we write tests? To improve quality! To reduce bugs! Because my senior told me to write it…

Slide 14

Slide 14 text

My answer is:

Slide 15

Slide 15 text

To make my development process easier.

Slide 16

Slide 16 text

With tests, we can: ✴ Express documentation / speci fi cation ✴ Ease code review ✴ Catch regressions early ✴ Improve design

Slide 17

Slide 17 text

But there are some problems in writing tests 😿

Slide 18

Slide 18 text

Using “real” things for everything gets painful… Unstable, Slow, Costly, or Out of Scope

Slide 19

Slide 19 text

So, we use test doubles.

Slide 20

Slide 20 text

I talk about: First-half The difference between mocks and stubs (Test doubles, mocks, stubs, Anti-patterns,…)

Slide 21

Slide 21 text

I talk about: Second-half Best test double practices in PHPUnit (Migration problems, and the fi xes)

Slide 22

Slide 22 text

#1 The difference between mocks and stubs

Slide 23

Slide 23 text

#1 The difference between mocks and stubs Mocks: Stubs: Assertion Imitation and Imitation The Difference Is

Slide 24

Slide 24 text

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

Slide 25

Slide 25 text

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

Slide 26

Slide 26 text

No Test Doubles #1 The difference between mocks and stubs

Slide 27

Slide 27 text

Use Mocks — Blurred a Little #1 The difference between mocks and stubs

Slide 28

Slide 28 text

Use Stubs — Blurred Pretty Much #1 The difference between mocks and stubs

Slide 29

Slide 29 text

createMock createStub Let's compare! #1 The difference between mocks and stubs

Slide 30

Slide 30 text

createMock createStub Target for mocking / stubbing #1 The difference between mocks and stubs

Slide 31

Slide 31 text

createMock createStub Imitation #1 The difference between mocks and stubs

Slide 32

Slide 32 text

createMock createStub Assertion #1 The difference between mocks and stubs

Slide 33

Slide 33 text

createMock createStub #1 The difference between mocks and stubs It is very simple.

Slide 34

Slide 34 text

#1 The difference between mocks and stubs ׬શʹཧղͨ͠ ͔ΜͥΜ Γ͔͍ kanzen / ni / rikai / shita

Slide 35

Slide 35 text

#1 The difference between mocks and stubs ׬શʹཧղͨ͠ ͔ΜͥΜ Γ͔͍ kanzen / ni / rikai / shita I am now an expert

Slide 36

Slide 36 text

When to Use Mocks vs. Stubs #1 The difference between mocks and stubs

Slide 37

Slide 37 text

My answer is whether the dependency belongs to the speci fi cation. #1 The difference between mocks and stubs

Slide 38

Slide 38 text

✴ 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

Slide 39

Slide 39 text

✴ 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

Slide 40

Slide 40 text

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

Slide 41

Slide 41 text

Overuse Antipatterns #1 The difference between mocks and stubs

Slide 42

Slide 42 text

✴ 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

Slide 43

Slide 43 text

Overused Mocks #1 The difference between mocks and stubs = Fragile Tests

Slide 44

Slide 44 text

✴ 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

Slide 45

Slide 45 text

#1 The difference between mocks and stubs Overused Stubs = Blank-Check Tests

Slide 46

Slide 46 text

✴ 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

Slide 47

Slide 47 text

Mocks or Stubs? Let the Spec Decide. #1 The difference between mocks and stubs

Slide 48

Slide 48 text

✴ 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

Slide 49

Slide 49 text

Tests Are Messages — to Your Teammates, to Your Future Self. #1 The difference between mocks and stubs

Slide 50

Slide 50 text

✴ 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

Slide 51

Slide 51 text

#2 Best Practices in the Latest Environment

Slide 52

Slide 52 text

Let's review how PHPUnit handles mocks and stubs. #2 Best Practices in the Latest Environment

Slide 53

Slide 53 text

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

Slide 54

Slide 54 text

We Got createMock() at First in PHPUnit 5.x But we still used createMock as stubs. #2 Best Practices in the Latest Environment

Slide 55

Slide 55 text

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

Slide 56

Slide 56 text

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

Slide 57

Slide 57 text

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

Slide 58

Slide 58 text

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

Slide 59

Slide 59 text

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

Slide 60

Slide 60 text

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

Slide 61

Slide 61 text

Best Practices for Handling Mocks and Stubs in PHPUnit Are Evolving. #2 Best Practices in the Latest Environment

Slide 62

Slide 62 text

✴ 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

Slide 63

Slide 63 text

Upgrade PHP to 8.5 and PHPUnit to 12.5.x #2 Best Practices in the Latest Environment

Slide 64

Slide 64 text

No content

Slide 65

Slide 65 text

A Vast Number of N

Slide 66

Slide 66 text

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

Slide 67

Slide 67 text

Here is an example #2 Best Practices in the Latest Environment

Slide 68

Slide 68 text

I made shared mocks in setUp #2 Best Practices in the Latest Environment

Slide 69

Slide 69 text

In tests, I use this getSUT method #2 Best Practices in the Latest Environment

Slide 70

Slide 70 text

And these are example tests #2 Best Practices in the Latest Environment

Slide 71

Slide 71 text

This test has all mocks properly con fi gured ✅ #2 Best Practices in the Latest Environment

Slide 72

Slide 72 text

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

Slide 73

Slide 73 text

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

Slide 74

Slide 74 text

The "Migration Reality" Is Happening everywhere. #2 Best Practices in the Latest Environment

Slide 75

Slide 75 text

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

Slide 76

Slide 76 text

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

Slide 77

Slide 77 text

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

Slide 78

Slide 78 text

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

Slide 79

Slide 79 text

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

Slide 80

Slide 80 text

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

Slide 81

Slide 81 text

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

Slide 82

Slide 82 text

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

Slide 83

Slide 83 text

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

Slide 84

Slide 84 text

And now, We Are on PHPUnit 13.x.x #2 Best Practices in the Latest Environment

Slide 85

Slide 85 text

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

Slide 86

Slide 86 text

The any() Matcher Is Hard Deprecated #2 Best Practices in the Latest Environment

Slide 87

Slide 87 text

The any() Matcher Is Hard Deprecated #2 Best Practices in the Latest Environment

Slide 88

Slide 88 text

The any() Matcher Is Hard Deprecated #2 Best Practices in the Latest Environment

Slide 89

Slide 89 text

The any() Matcher Is Hard Deprecated #2 Best Practices in the Latest Environment

Slide 90

Slide 90 text

The any() Matcher Is Hard Deprecated #2 Best Practices in the Latest Environment

Slide 91

Slide 91 text

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

Slide 92

Slide 92 text

with*() Without expects() Is Hard Deprecated #2 Best Practices in the Latest Environment

Slide 93

Slide 93 text

#2 Best Practices in the Latest Environment with*() Without expects() Is Hard Deprecated

Slide 94

Slide 94 text

#2 Best Practices in the Latest Environment with*() Without expects() Is Hard Deprecated

Slide 95

Slide 95 text

#2 Best Practices in the Latest Environment with*() Without expects() Is Hard Deprecated

Slide 96

Slide 96 text

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

Slide 97

Slide 97 text

atLeast() With a Non-Positive Argument Is Hard Deprecated #2 Best Practices in the Latest Environment

Slide 98

Slide 98 text

atLeast() With a Non-Positive Argument Is Hard Deprecated #2 Best Practices in the Latest Environment

Slide 99

Slide 99 text

atLeast() With a Non-Positive Argument Is Hard Deprecated #2 Best Practices in the Latest Environment

Slide 100

Slide 100 text

atLeast() With a Non-Positive Argument Is Hard Deprecated #2 Best Practices in the Latest Environment

Slide 101

Slide 101 text

atLeast() With a Non-Positive Argument Is Hard Deprecated #2 Best Practices in the Latest Environment

Slide 102

Slide 102 text

✴ 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

Slide 103

Slide 103 text

Takeaways #2 Best Practices in the Latest Environment

Slide 104

Slide 104 text

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

Slide 105

Slide 105 text

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

Slide 106

Slide 106 text

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

Slide 107

Slide 107 text

#3 Summary

Slide 108

Slide 108 text

I write test code to make my development process easier.

Slide 109

Slide 109 text

If I need test double with imitation and assertion, I use mocks. If only imitation, I use stubs.

Slide 110

Slide 110 text

PHPUnit is evolving to distinguish stubs from mocks, Let's make a clear distinction between them.

Slide 111

Slide 111 text

You Know What to Say…😋

Slide 112

Slide 112 text

׬શʹཧղͨ͠ ͔ΜͥΜ Γ͔͍ kanzen / ni / rikai / shita

Slide 113

Slide 113 text

And,

Slide 114

Slide 114 text

I love PHPUnit! Thank you Sebastian Bergmann🫶

Slide 115

Slide 115 text

He gave the keynote speech in PHP Conference Odawara

Slide 116

Slide 116 text

Brought PHPUnit collaboration merch for my hosted conference.

Slide 117

Slide 117 text

🍊͋Γ͕ͱ͏🍊 Thanks! Enjoy your 2 days!!