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
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
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
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
✴ 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
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
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
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
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
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
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
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
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
✴ 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
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
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
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
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
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
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
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
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
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
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