design how your objects talk through mocking
DESCRIPTION
What should you test with your unit tests? Some people will say that unit behaviour is best tested through it's outcomes. But what if communication between units itself is more important than the results of it? This session will introduce you to two different ways of unit-testing and show you a way to assert your object behaviours through their communications.TRANSCRIPT
![Page 1: Design how your objects talk through mocking](https://reader035.vdocuments.us/reader035/viewer/2022081412/53fdef1d8d7f72a81c8b4be6/html5/thumbnails/1.jpg)
Design how your objects talk
through mocking
![Page 2: Design how your objects talk through mocking](https://reader035.vdocuments.us/reader035/viewer/2022081412/53fdef1d8d7f72a81c8b4be6/html5/thumbnails/2.jpg)
”– Reverse Focus on the reverse mortgages
“One of the most common mistakes people make is to fixate on the goal or expected outcome while ignoring their underlying
behaviours.”
![Page 3: Design how your objects talk through mocking](https://reader035.vdocuments.us/reader035/viewer/2022081412/53fdef1d8d7f72a81c8b4be6/html5/thumbnails/3.jpg)
@everzet• BDD Practice Manager• Software Engineer• Creator of Behat, Mink,
Prophecy, PhpSpec2• Contributor to Symfony2,
Doctrine2, Composer
![Page 4: Design how your objects talk through mocking](https://reader035.vdocuments.us/reader035/viewer/2022081412/53fdef1d8d7f72a81c8b4be6/html5/thumbnails/4.jpg)
This talk is about
• Test-driven development with and without mocks
• Introducing and making sense of different types of doubles
• OOP as a messaging paradigm
• Software design as a response to messaging observations
• Code
![Page 5: Design how your objects talk through mocking](https://reader035.vdocuments.us/reader035/viewer/2022081412/53fdef1d8d7f72a81c8b4be6/html5/thumbnails/5.jpg)
Test-driven development
By Example!
“The TDD book”!
Circa 2002
![Page 6: Design how your objects talk through mocking](https://reader035.vdocuments.us/reader035/viewer/2022081412/53fdef1d8d7f72a81c8b4be6/html5/thumbnails/6.jpg)
Money multiplication test from the TDD book
public void testMultiplication() { Dollar five = new Dollar(5); Dollar product = five.times(2); ! assertEquals(10, product.amount); ! product = five.times(3); ! assertEquals(15, product.amount); }
![Page 7: Design how your objects talk through mocking](https://reader035.vdocuments.us/reader035/viewer/2022081412/53fdef1d8d7f72a81c8b4be6/html5/thumbnails/7.jpg)
Money multiplication test in PHP
public function testMultiplication() { $five = new Dollar(5); $product = $five->times(2); $this->assertEquals(10, $product->getAmount()); $product = $five->times(3); $this->assertEquals(15, $product->getAmount()); }
![Page 8: Design how your objects talk through mocking](https://reader035.vdocuments.us/reader035/viewer/2022081412/53fdef1d8d7f72a81c8b4be6/html5/thumbnails/8.jpg)
Event dispatching test
public function testEventIsDispatchedDuringRegistration() { $dispatcher = new EventDispatcher(); $repository = new UserRepository(); $manager = new UserManager($repository, $dispatcher); ! $timesDispatched = 0; $dispatcher->addListener( 'userIsRegistered', function() use($timesDispatched) { $timesDispatched += 1; } ); ! $user = User::signup('[email protected]'); $manager->registerUser($user); $this->assertSame(1, $timesDispatched); }
![Page 9: Design how your objects talk through mocking](https://reader035.vdocuments.us/reader035/viewer/2022081412/53fdef1d8d7f72a81c8b4be6/html5/thumbnails/9.jpg)
Event dispatching test
public function testEventIsDispatchedDuringRegistration() { $dispatcher = new EventDispatcher(); $repository = new UserRepository(); $manager = new UserManager($repository, $dispatcher); ! $timesDispatched = 0; $dispatcher->addListener( 'userIsRegistered', function() use($timesDispatched) { $timesDispatched += 1; } ); ! $user = User::signup('[email protected]'); $manager->registerUser($user); $this->assertSame(1, $timesDispatched); }
![Page 10: Design how your objects talk through mocking](https://reader035.vdocuments.us/reader035/viewer/2022081412/53fdef1d8d7f72a81c8b4be6/html5/thumbnails/10.jpg)
![Page 11: Design how your objects talk through mocking](https://reader035.vdocuments.us/reader035/viewer/2022081412/53fdef1d8d7f72a81c8b4be6/html5/thumbnails/11.jpg)
![Page 12: Design how your objects talk through mocking](https://reader035.vdocuments.us/reader035/viewer/2022081412/53fdef1d8d7f72a81c8b4be6/html5/thumbnails/12.jpg)
”– Ralph Waldo Emerson
“Life is a journey, not a destination.”
![Page 13: Design how your objects talk through mocking](https://reader035.vdocuments.us/reader035/viewer/2022081412/53fdef1d8d7f72a81c8b4be6/html5/thumbnails/13.jpg)
Growing Object-Oriented
Software,Guided by Tests
!“The GOOS book”
!Circa 2009
![Page 14: Design how your objects talk through mocking](https://reader035.vdocuments.us/reader035/viewer/2022081412/53fdef1d8d7f72a81c8b4be6/html5/thumbnails/14.jpg)
Event dispatching test
public function testEventIsDispatchedDuringRegistration() { $dispatcher = new EventDispatcher(); $repository = new UserRepository(); $manager = new UserManager($repository, $dispatcher); ! $timesDispatched = 0; $dispatcher->addListener( 'userIsRegistered', function() use($timesDispatched) { $timesDispatched += 1; } ); ! $user = User::signup('[email protected]'); $manager->registerUser($user); $this->assertSame(1, $timesDispatched); }
![Page 15: Design how your objects talk through mocking](https://reader035.vdocuments.us/reader035/viewer/2022081412/53fdef1d8d7f72a81c8b4be6/html5/thumbnails/15.jpg)
Event dispatching collaborators
public function testEventIsDispatchedDuringRegistration() { $dispatcher = new EventDispatcher(); $repository = new UserRepository(); $manager = new UserManager($repository, $dispatcher); ! $timesDispatched = 0; $dispatcher->addListener( 'userIsRegistered', function() use($timesDispatched) { $timesDispatched += 1; } ); ! $user = User::signup('[email protected]'); $manager->registerUser($user); $this->assertSame(1, $timesDispatched); }
![Page 16: Design how your objects talk through mocking](https://reader035.vdocuments.us/reader035/viewer/2022081412/53fdef1d8d7f72a81c8b4be6/html5/thumbnails/16.jpg)
Find the message
public function testEventIsDispatchedDuringRegistration() { $dispatcher = new EventDispatcher(); $repository = new UserRepository(); $manager = new UserManager($repository, $dispatcher); ! $timesDispatched = 0; $dispatcher->addListener( 'userIsRegistered', function() use($timesDispatched) { $timesDispatched += 1; } ); ! $user = User::signup('[email protected]'); $manager->registerUser($user); $this->assertSame(1, $timesDispatched); }
![Page 17: Design how your objects talk through mocking](https://reader035.vdocuments.us/reader035/viewer/2022081412/53fdef1d8d7f72a81c8b4be6/html5/thumbnails/17.jpg)
messages or state
![Page 18: Design how your objects talk through mocking](https://reader035.vdocuments.us/reader035/viewer/2022081412/53fdef1d8d7f72a81c8b4be6/html5/thumbnails/18.jpg)
”– Alan Kay, father of OOP
“OOP to me means only messaging, local retention and protection and hiding of state-
process, and extreme late-binding of all things.”
![Page 19: Design how your objects talk through mocking](https://reader035.vdocuments.us/reader035/viewer/2022081412/53fdef1d8d7f72a81c8b4be6/html5/thumbnails/19.jpg)
Interfaces
interface LoginMessenger { public function askForCard(); public function askForPin(); } interface InputMessenger { public function askForAccount(); public function askForAmount(); } interface WithdrawalMessenger { public function tellNoMoney(); public function tellMachineEmpty(); }
![Page 20: Design how your objects talk through mocking](https://reader035.vdocuments.us/reader035/viewer/2022081412/53fdef1d8d7f72a81c8b4be6/html5/thumbnails/20.jpg)
Doubles
1. Dummy
2. Stub
3. Spy
4. Mock
5. Fake
![Page 21: Design how your objects talk through mocking](https://reader035.vdocuments.us/reader035/viewer/2022081412/53fdef1d8d7f72a81c8b4be6/html5/thumbnails/21.jpg)
Prophecy
(1) use Prophecy\Prophet;
(2) use Prophecy\Argument;
(3) $prophet = new Prophet();
(4) $userProphecy = $prophet->prophesize(UserInterface::class);
(5) $userProphecy->changeName('everzet')->shouldBeCalled();
(6) $user = $userProphecy->reveal();
(7) $user->changeName('_md');
(8) $prophet->checkPredictions();
![Page 22: Design how your objects talk through mocking](https://reader035.vdocuments.us/reader035/viewer/2022081412/53fdef1d8d7f72a81c8b4be6/html5/thumbnails/22.jpg)
1. Dummy
![Page 23: Design how your objects talk through mocking](https://reader035.vdocuments.us/reader035/viewer/2022081412/53fdef1d8d7f72a81c8b4be6/html5/thumbnails/23.jpg)
1. Dummy
class System { private $authorizer; public function __construct(Authorizer $authorizer) { $this->authorizer = $authorizer; } public function getLoginCount() { return 0; } }
![Page 24: Design how your objects talk through mocking](https://reader035.vdocuments.us/reader035/viewer/2022081412/53fdef1d8d7f72a81c8b4be6/html5/thumbnails/24.jpg)
1. Dummy
class System { private $authorizer; public function __construct(Authorizer $authorizer) { $this->authorizer = $authorizer; } public function getLoginCount() { return 0; } } !public function testNewlyCreatedSystemHasNoLoggedInUsers() { $auth = $this->prophesize(Authorizer::class); $system = new System($auth->reveal()); $this->assertSame(0, $system->getLoginCount()); }
![Page 25: Design how your objects talk through mocking](https://reader035.vdocuments.us/reader035/viewer/2022081412/53fdef1d8d7f72a81c8b4be6/html5/thumbnails/25.jpg)
1. Dummy
class System { private $authorizer; public function __construct(Authorizer $authorizer) { $this->authorizer = $authorizer; } public function getLoginCount() { return 0; } } !public function testNewlyCreatedSystemHasNoLoggedInUsers() { $auth = $this->prophesize(Authorizer::class); $system = new System($auth->reveal()); $this->assertSame(0, $system->getLoginCount()); }
![Page 26: Design how your objects talk through mocking](https://reader035.vdocuments.us/reader035/viewer/2022081412/53fdef1d8d7f72a81c8b4be6/html5/thumbnails/26.jpg)
2. Stub
![Page 27: Design how your objects talk through mocking](https://reader035.vdocuments.us/reader035/viewer/2022081412/53fdef1d8d7f72a81c8b4be6/html5/thumbnails/27.jpg)
2. Stub
class System { // ... ! public function logIn($username, $password) { if ($this->authorizer->authorize($username, $password)) { $this->loginCount++; } } ! public function getLoginCount() { return $this->loginCount; } }
![Page 28: Design how your objects talk through mocking](https://reader035.vdocuments.us/reader035/viewer/2022081412/53fdef1d8d7f72a81c8b4be6/html5/thumbnails/28.jpg)
2. Stub
class System { // ... ! public function logIn($username, $password) { if ($this->authorizer->authorize($username, $password)) { $this->loginCount++; } } ! public function getLoginCount() { return $this->loginCount; } } !public function testCountsSuccessfullyAuthorizedLogIns() { $auth = $this->prophesize(Authorizer::class); $system = new System($auth->reveal()); ! $auth->authorize('everzet', '123')->willReturn(true); ! $system->logIn('everzet', ‘123’); ! $this->assertSame(1, $system->getLoginCount()); }
![Page 29: Design how your objects talk through mocking](https://reader035.vdocuments.us/reader035/viewer/2022081412/53fdef1d8d7f72a81c8b4be6/html5/thumbnails/29.jpg)
2. Stub
class System { // ... ! public function logIn($username, $password) { if ($this->authorizer->authorize($username, $password)) { $this->loginCount++; } } ! public function getLoginCount() { return $this->loginCount; } } !public function testCountsSuccessfullyAuthorizedLogIns() { $auth = $this->prophesize(Authorizer::class); $system = new System($auth->reveal()); ! $auth->authorize('everzet', '123')->willReturn(true); ! $system->logIn('everzet', ‘123’); ! $this->assertSame(1, $system->getLoginCount()); }
![Page 30: Design how your objects talk through mocking](https://reader035.vdocuments.us/reader035/viewer/2022081412/53fdef1d8d7f72a81c8b4be6/html5/thumbnails/30.jpg)
2. Stub
class System { // ... ! public function logIn($username, $password) { if ($this->authorizer->authorize($username, $password)) { $this->loginCount++; } } ! public function getLoginCount() { return $this->loginCount; } } !public function testCountsSuccessfullyAuthorizedLogIns() { $auth = $this->prophesize(Authorizer::class); $system = new System($auth->reveal()); ! $auth->authorize('everzet', '123')->willReturn(true); ! $system->logIn(‘_md', ‘321’); ! $this->assertSame(1, $system->getLoginCount()); }
![Page 31: Design how your objects talk through mocking](https://reader035.vdocuments.us/reader035/viewer/2022081412/53fdef1d8d7f72a81c8b4be6/html5/thumbnails/31.jpg)
3. Spy
![Page 32: Design how your objects talk through mocking](https://reader035.vdocuments.us/reader035/viewer/2022081412/53fdef1d8d7f72a81c8b4be6/html5/thumbnails/32.jpg)
3. Spy
class System { // ... ! public function logIn($username, $password) { if ($this->authorizer->authorize($username, $password)) { $this->loginCount++; $this->lastLoginTimer->recordLogin($username); } } }
![Page 33: Design how your objects talk through mocking](https://reader035.vdocuments.us/reader035/viewer/2022081412/53fdef1d8d7f72a81c8b4be6/html5/thumbnails/33.jpg)
3. Spy
class System { // ... ! public function logIn($username, $password) { if ($this->authorizer->authorize($username, $password)) { $this->loginCount++; $this->lastLoginTimer->recordLogin($username); } } } !public function testCountsSuccessfullyAuthorizedLogIns() { $auth = $this->prophesize(Authorizer::class); $timer = $this->prophesize(LoginTimer::class); $system = new System($auth->reveal(), $timer->reveal()); $auth->authorize('everzet', '123')->willReturn(true); ! $system->login('everzet', '123'); ! $timer->recordLogin('everzet')->shouldHaveBeenCalled(); }
![Page 34: Design how your objects talk through mocking](https://reader035.vdocuments.us/reader035/viewer/2022081412/53fdef1d8d7f72a81c8b4be6/html5/thumbnails/34.jpg)
3. Spy
class System { // ... ! public function logIn($username, $password) { if ($this->authorizer->authorize($username, $password)) { $this->loginCount++; $this->lastLoginTimer->recordLogin($username); } } } !public function testCountsSuccessfullyAuthorizedLogIns() { $auth = $this->prophesize(Authorizer::class); $timer = $this->prophesize(LoginTimer::class); $system = new System($auth->reveal(), $timer->reveal()); $auth->authorize('everzet', '123')->willReturn(true); ! $system->login('everzet', '123'); ! $timer->recordLogin('everzet')->shouldHaveBeenCalled(); }
![Page 35: Design how your objects talk through mocking](https://reader035.vdocuments.us/reader035/viewer/2022081412/53fdef1d8d7f72a81c8b4be6/html5/thumbnails/35.jpg)
4. Mock
![Page 36: Design how your objects talk through mocking](https://reader035.vdocuments.us/reader035/viewer/2022081412/53fdef1d8d7f72a81c8b4be6/html5/thumbnails/36.jpg)
3. Spy
class System { // ... ! public function logIn($username, $password) { if ($this->authorizer->authorize($username, $password)) { $this->loginCount++; $this->lastLoginTimer->recordLogin($username); } } } !public function testCountsSuccessfullyAuthorizedLogIns() { $auth = $this->prophesize(Authorizer::class); $timer = $this->prophesize(LoginTimer::class); $system = new System($auth->reveal(), $timer->reveal()); $auth->authorize('everzet', '123')->willReturn(true); ! $system->login('everzet', '123'); ! $timer->recordLogin('everzet')->shouldHaveBeenCalled(); }
![Page 37: Design how your objects talk through mocking](https://reader035.vdocuments.us/reader035/viewer/2022081412/53fdef1d8d7f72a81c8b4be6/html5/thumbnails/37.jpg)
4. Mock
class System { // ... ! public function logIn($username, $password) { if ($this->authorizer->authorize($username, $password)) { $this->loginCount++; $this->lastLoginTimer->recordLogin($username); } } } !public function testCountsSuccessfullyAuthorizedLogIns() { $auth = $this->prophesize(Authorizer::class); $timer = $this->prophesize(LoginTimer::class); $system = new System($auth->reveal(), $timer->reveal()); $auth->authorize('everzet', '123')->willReturn(true); ! $timer->recordLogin('everzet')->shouldBeCalled(); ! $system->login('everzet', '123'); ! $this->getProphet()->checkPredictions(); }
![Page 38: Design how your objects talk through mocking](https://reader035.vdocuments.us/reader035/viewer/2022081412/53fdef1d8d7f72a81c8b4be6/html5/thumbnails/38.jpg)
Back to the event dispatcher
![Page 39: Design how your objects talk through mocking](https://reader035.vdocuments.us/reader035/viewer/2022081412/53fdef1d8d7f72a81c8b4be6/html5/thumbnails/39.jpg)
Find the message
public function testEventIsDispatchedDuringRegistration() { $dispatcher = new EventDispatcher(); $repository = new UserRepository(); $manager = new UserManager($repository, $dispatcher); ! $timesDispatched = 0; $dispatcher->addListener( 'userIsRegistered', function() use($timesDispatched) { $timesDispatched += 1; } ); ! $user = User::signup('[email protected]'); $manager->registerUser($user); $this->assertSame(1, $timesDispatched); }
![Page 40: Design how your objects talk through mocking](https://reader035.vdocuments.us/reader035/viewer/2022081412/53fdef1d8d7f72a81c8b4be6/html5/thumbnails/40.jpg)
Communication over state
public function testEventIsDispatchedDuringRegistration() { $repository = $this->prophesize(UserRepository::class); $dispatcher = $this->prophesize(EventDispatcher::class); $manager = new UserManager( $repository->reveal(), $dispatcher->reveal() ); $user = User::signup('[email protected]'); $manager->registerUser($user); ! $dispatcher->dispatch('userIsRegistered', Argument::any()) ->shouldHaveBeenCalled(); }
![Page 41: Design how your objects talk through mocking](https://reader035.vdocuments.us/reader035/viewer/2022081412/53fdef1d8d7f72a81c8b4be6/html5/thumbnails/41.jpg)
Exposed communication
public function testEventIsDispatchedDuringRegistration() { $repository = $this->prophesize(UserRepository::class); $dispatcher = $this->prophesize(EventDispatcher::class); $manager = new UserManager( $repository->reveal(), $dispatcher->reveal() ); $user = User::signup('[email protected]'); $manager->registerUser($user); ! $dispatcher->dispatch('userIsRegistered', Argument::any()) ->shouldHaveBeenCalled(); }
![Page 42: Design how your objects talk through mocking](https://reader035.vdocuments.us/reader035/viewer/2022081412/53fdef1d8d7f72a81c8b4be6/html5/thumbnails/42.jpg)
Design?
![Page 43: Design how your objects talk through mocking](https://reader035.vdocuments.us/reader035/viewer/2022081412/53fdef1d8d7f72a81c8b4be6/html5/thumbnails/43.jpg)
”– The Observer Effect
“The act of observing will influence the phenomenon being observed.”
![Page 44: Design how your objects talk through mocking](https://reader035.vdocuments.us/reader035/viewer/2022081412/53fdef1d8d7f72a81c8b4be6/html5/thumbnails/44.jpg)
The 1st case: simple controller
![Page 45: Design how your objects talk through mocking](https://reader035.vdocuments.us/reader035/viewer/2022081412/53fdef1d8d7f72a81c8b4be6/html5/thumbnails/45.jpg)
Simple Symfony2 controller
public function packagesListAction(Request $req, User $user) { $packages = $this->getDoctrine() ->getRepository('WebBundle:Package') ->getFilteredQueryBuilder(array('maintainer' => $user->getId())) ->orderBy('p.name') ->getQuery() ->execute(); ! return $this->render('WebBundle:User:packages.html.twig', [ 'packages' => $packages ]); }
![Page 46: Design how your objects talk through mocking](https://reader035.vdocuments.us/reader035/viewer/2022081412/53fdef1d8d7f72a81c8b4be6/html5/thumbnails/46.jpg)
“Simple” Symfony2 controller test
public function testShowMaintainedPackages() { $request = new Request(); $user = new User('everzet'); $container = $this->prophesize(ContainerInterface::class); $doctrine = $this->prophesize(EntityManager::class); $repository = $this->prophesize(PackageRepository::class); $queryBuilder = $this->prophesize(QueryBuilder::class); $query = $this->prophesize(Query::class); $packages = [new Package('Behat'), new Package('PhpSpec')]; $templating = $this->prophesize(EngineInterface::class); $response = new Response('User packages'); ! $container->get('doctrine.orm')->willReturn($doctrine); $doctrine->getRepository('WebBundle:Package')->willReturn($repository); $repository->getFilteredQueryBuilder(['maintainer' => $user->getId()]) ->willReturn($queryBuilder); $queryBuilder->orderBy('p.name')->shouldBeCalled(); $queryBuilder->getQuery()->willReturn($query); $query->execute()->willReturn($packages); $templating->renderResponse( 'WebBundle:User:packages.html.twig', ['packages' => $packages], null) ->willReturn($response); ! $controller = new UserController(); $controller->setContainer($container); $controllerResult = $controller->maintainsPackagesAction($request, $user); ! $this->assertEquals($response, $controllerResult); }
![Page 47: Design how your objects talk through mocking](https://reader035.vdocuments.us/reader035/viewer/2022081412/53fdef1d8d7f72a81c8b4be6/html5/thumbnails/47.jpg)
“Simple” Symfony2 controller test
public function testShowMaintainedPackages() { $request = new Request(); $user = new User('everzet'); $container = $this->prophesize(ContainerInterface::class); $doctrine = $this->prophesize(EntityManager::class); $repository = $this->prophesize(PackageRepository::class); $queryBuilder = $this->prophesize(QueryBuilder::class); $query = $this->prophesize(Query::class); $packages = [new Package('Behat'), new Package('PhpSpec')]; $templating = $this->prophesize(EngineInterface::class); $response = new Response('User packages'); ! $container->get('doctrine.orm')->willReturn($doctrine); $doctrine->getRepository('WebBundle:Package')->willReturn($repository); $repository->getFilteredQueryBuilder(['maintainer' => $user->getId()]) ->willReturn($queryBuilder); $queryBuilder->orderBy('p.name')->shouldBeCalled(); $queryBuilder->getQuery()->willReturn($query); $query->execute()->willReturn($packages); $templating->renderResponse( 'WebBundle:User:packages.html.twig', ['packages' => $packages], null) ->willReturn($response); ! $controller = new UserController(); $controller->setContainer($container); $controllerResult = $controller->maintainsPackagesAction($request, $user); ! $this->assertEquals($response, $controllerResult); }
![Page 48: Design how your objects talk through mocking](https://reader035.vdocuments.us/reader035/viewer/2022081412/53fdef1d8d7f72a81c8b4be6/html5/thumbnails/48.jpg)
“Simple” Symfony2 controller test
public function testShowMaintainedPackages() { $request = new Request(); $user = new User('everzet'); $container = $this->prophesize(ContainerInterface::class); $doctrine = $this->prophesize(EntityManager::class); $repository = $this->prophesize(PackageRepository::class); $queryBuilder = $this->prophesize(QueryBuilder::class); $query = $this->prophesize(Query::class); $packages = [new Package('Behat'), new Package('PhpSpec')]; $templating = $this->prophesize(EngineInterface::class); $response = new Response('User packages'); ! $container->get('doctrine.orm')->willReturn($doctrine); $doctrine->getRepository('WebBundle:Package')->willReturn($repository); $repository->getFilteredQueryBuilder(['maintainer' => $user->getId()]) ->willReturn($queryBuilder); $queryBuilder->orderBy('p.name')->shouldBeCalled(); $queryBuilder->getQuery()->willReturn($query); $query->execute()->willReturn($packages); $templating->renderResponse( 'WebBundle:User:packages.html.twig', ['packages' => $packages], null) ->willReturn($response); ! $controller = new UserController(); $controller->setContainer($container); $controllerResult = $controller->maintainsPackagesAction($request, $user); ! $this->assertEquals($response, $controllerResult); }
![Page 49: Design how your objects talk through mocking](https://reader035.vdocuments.us/reader035/viewer/2022081412/53fdef1d8d7f72a81c8b4be6/html5/thumbnails/49.jpg)
Single Responsibility Principle
![Page 50: Design how your objects talk through mocking](https://reader035.vdocuments.us/reader035/viewer/2022081412/53fdef1d8d7f72a81c8b4be6/html5/thumbnails/50.jpg)
Simpler Symfony2 controller simple test
public function testShowMaintainedPackages() { $user = new User('everzet'); $repository = $this->prophesize(PackageRepository::class); $templating = $this->prophesize(EngineInterface::class); $packages = [new Package('Behat'), new Package('PhpSpec')]; $response = new Response('User packages'); ! $repository->getMaintainedPackagesOrderedByName($user)->willReturn($packages); $templating->renderResponse( 'WebBundle:User:packages.html.twig', ['packages' => $packages], null) ->willReturn($response); ! $controller = new UserController($repository->reveal(), $templating->reveal()); $controllerResult = $controller->maintainsPackagesAction($user); ! $this->assertEquals($response, $controllerResult); }
![Page 51: Design how your objects talk through mocking](https://reader035.vdocuments.us/reader035/viewer/2022081412/53fdef1d8d7f72a81c8b4be6/html5/thumbnails/51.jpg)
Simpler Symfony2 controller simple test
public function testShowMaintainedPackages() { $user = new User('everzet'); $repository = $this->prophesize(PackageRepository::class); $templating = $this->prophesize(EngineInterface::class); $packages = [new Package('Behat'), new Package('PhpSpec')]; $response = new Response('User packages'); ! $repository->getMaintainedPackagesOrderedByName($user)->willReturn($packages); $templating->renderResponse( 'WebBundle:User:packages.html.twig', ['packages' => $packages], null) ->willReturn($response); ! $controller = new UserController($repository->reveal(), $templating->reveal()); $controllerResult = $controller->maintainsPackagesAction($user); ! $this->assertEquals($response, $controllerResult); }
![Page 52: Design how your objects talk through mocking](https://reader035.vdocuments.us/reader035/viewer/2022081412/53fdef1d8d7f72a81c8b4be6/html5/thumbnails/52.jpg)
Simpler Symfony2 controller
public function maintainsPackagesAction(User $user) { $packages = $this->repo->getMaintainedPackagesOrderedByName($user); ! return $this->tpl->renderResponse('WebBundle:User:packages.html.twig', [ 'packages' => $packages ]); }
![Page 53: Design how your objects talk through mocking](https://reader035.vdocuments.us/reader035/viewer/2022081412/53fdef1d8d7f72a81c8b4be6/html5/thumbnails/53.jpg)
The 2nd case: basket checkout
![Page 54: Design how your objects talk through mocking](https://reader035.vdocuments.us/reader035/viewer/2022081412/53fdef1d8d7f72a81c8b4be6/html5/thumbnails/54.jpg)
Basket checkout
class Basket { // ... ! public function checkout(OrderProcessor $processor) { $totalPrice = new Price::free(); foreach ($this->getItems() as $item) { $totalPrice = $totalPrice->add($item->getPrice()); $processor->addItem($item); } ! $payment = new CashPayment::fromPrice($totalPrice); $processor->setPayment($payment); $processor->pay(); } }
![Page 55: Design how your objects talk through mocking](https://reader035.vdocuments.us/reader035/viewer/2022081412/53fdef1d8d7f72a81c8b4be6/html5/thumbnails/55.jpg)
Basket checkout test
public function testCheckout() { $items = [new Item(Price::fromInt(10), Price::fromInt(5)]; $processor = $this->prophesize(OrderProcessor::class); $basket = new Basket($items); $basket->checkout($processor->reveal()); $processor->addItem($items[0])->shouldHaveBeenCalled(); $processor->addItem($items[1])->shouldHaveBeenCalled(); $processor->setPayment(Argument::which('getPrice', 15))->shouldHaveBeenCalled(); $processor->pay()->shouldHaveBeenCalled(); }
![Page 56: Design how your objects talk through mocking](https://reader035.vdocuments.us/reader035/viewer/2022081412/53fdef1d8d7f72a81c8b4be6/html5/thumbnails/56.jpg)
Basket checkout test two payments
public function testCheckoutWithCash() { $items = [new Item(Price::fromInt(10), Price::fromInt(5)]; $processor = $this->prophesize(OrderProcessor::class); $basket = new Basket($items, $credit = false); $basket->checkout($processor->reveal()); $processor->addItem($items[0])->shouldHaveBeenCalled(); $processor->addItem($items[1])->shouldHaveBeenCalled(); $processor->setPayment(Argument::allOf( Argument::type(CashPayment::class), Argument::which('getPrice', 15) ))->shouldHaveBeenCalled(); $processor->pay()->shouldHaveBeenCalled(); } public function testCheckoutWithCreditCard() { $items = [new Item(Price::fromInt(10), Price::fromInt(5)]; $processor = $this->prophesize(OrderProcessor::class); $basket = new Basket($items, $credit = true); $basket->checkout($processor->reveal()); $processor->addItem($items[0])->shouldHaveBeenCalled(); $processor->addItem($items[1])->shouldHaveBeenCalled(); $processor->setPayment(Argument::allOf( Argument::type(CreditPayment::class), Argument::which('getPrice', 15) ))->shouldHaveBeenCalled(); $processor->pay()->shouldHaveBeenCalled(); }
![Page 57: Design how your objects talk through mocking](https://reader035.vdocuments.us/reader035/viewer/2022081412/53fdef1d8d7f72a81c8b4be6/html5/thumbnails/57.jpg)
Basket checkout test two payments
public function testCheckoutWithCash() { $items = [new Item(Price::fromInt(10), Price::fromInt(5)]; $processor = $this->prophesize(OrderProcessor::class); $basket = new Basket($items, $credit = false); $basket->checkout($processor->reveal()); $processor->addItem($items[0])->shouldHaveBeenCalled(); $processor->addItem($items[1])->shouldHaveBeenCalled(); $processor->setPayment(Argument::allOf( Argument::type(CashPayment::class), Argument::which('getPrice', 15) ))->shouldHaveBeenCalled(); $processor->pay()->shouldHaveBeenCalled(); } public function testCheckoutWithCreditCard() { $items = [new Item(Price::fromInt(10), Price::fromInt(5)]; $processor = $this->prophesize(OrderProcessor::class); $basket = new Basket($items, $credit = true); $basket->checkout($processor->reveal()); $processor->addItem($items[0])->shouldHaveBeenCalled(); $processor->addItem($items[1])->shouldHaveBeenCalled(); $processor->setPayment(Argument::allOf( Argument::type(CreditPayment::class), Argument::which('getPrice', 15) ))->shouldHaveBeenCalled(); $processor->pay()->shouldHaveBeenCalled(); }
![Page 58: Design how your objects talk through mocking](https://reader035.vdocuments.us/reader035/viewer/2022081412/53fdef1d8d7f72a81c8b4be6/html5/thumbnails/58.jpg)
Basket checkout duplication in test
public function testCheckoutWithCash() { $items = [new Item(Price::fromInt(10), Price::fromInt(5)]; $processor = $this->prophesize(OrderProcessor::class); $basket = new Basket($items, $credit = false); $basket->checkout($processor->reveal()); $processor->addItem($items[0])->shouldHaveBeenCalled(); $processor->addItem($items[1])->shouldHaveBeenCalled(); $processor->setPayment(Argument::allOf( Argument::type(CashPayment::class), Argument::which('getPrice', 15) ))->shouldHaveBeenCalled(); $processor->pay()->shouldHaveBeenCalled(); } public function testCheckoutWithCreditCard() { $items = [new Item(Price::fromInt(10), Price::fromInt(5)]; $processor = $this->prophesize(OrderProcessor::class); $basket = new Basket($items, $credit = true); $basket->checkout($processor->reveal()); $processor->addItem($items[0])->shouldHaveBeenCalled(); $processor->addItem($items[1])->shouldHaveBeenCalled(); $processor->setPayment(Argument::allOf( Argument::type(CreditPayment::class), Argument::which('getPrice', 15) ))->shouldHaveBeenCalled(); $processor->pay()->shouldHaveBeenCalled(); }
![Page 59: Design how your objects talk through mocking](https://reader035.vdocuments.us/reader035/viewer/2022081412/53fdef1d8d7f72a81c8b4be6/html5/thumbnails/59.jpg)
Open Closed Principle
![Page 60: Design how your objects talk through mocking](https://reader035.vdocuments.us/reader035/viewer/2022081412/53fdef1d8d7f72a81c8b4be6/html5/thumbnails/60.jpg)
Basket checkout test simplification
public function testCheckoutWithPaymentMethod() { $items = [new Item(Price::fromInt(10), Price::fromInt(5)]; $processor = $this->prophesize(OrderProcessor::class); $paymentMethod = $this->prophesize(PaymentMethod::class); $payment = $this->prophesize(Payment::class); $paymentMethod->acceptPayment(Price::fromInt(15))->willReturn($payment); $basket = new Basket($items); $basket->checkout($processor->reveal(), $paymentMethod->reveal()); $processor->addItem($items[0])->shouldHaveBeenCalled(); $processor->addItem($items[1])->shouldHaveBeenCalled(); $processor->setPayment($payment)->shouldHaveBeenCalled(); $processor->pay()->shouldHaveBeenCalled(); }
![Page 61: Design how your objects talk through mocking](https://reader035.vdocuments.us/reader035/viewer/2022081412/53fdef1d8d7f72a81c8b4be6/html5/thumbnails/61.jpg)
Basket checkout test simplification
public function testCheckoutWithPaymentMethod() { $items = [new Item(Price::fromInt(10), Price::fromInt(5)]; $processor = $this->prophesize(OrderProcessor::class); $paymentMethod = $this->prophesize(PaymentMethod::class); $payment = $this->prophesize(Payment::class); $paymentMethod->acceptPayment(Price::fromInt(15))->willReturn($payment); $basket = new Basket($items); $basket->checkout($processor->reveal(), $paymentMethod->reveal()); $processor->addItem($items[0])->shouldHaveBeenCalled(); $processor->addItem($items[1])->shouldHaveBeenCalled(); $processor->setPayment($payment)->shouldHaveBeenCalled(); $processor->pay()->shouldHaveBeenCalled(); }
![Page 62: Design how your objects talk through mocking](https://reader035.vdocuments.us/reader035/viewer/2022081412/53fdef1d8d7f72a81c8b4be6/html5/thumbnails/62.jpg)
Final basket checkout
class Basket { // ... public function checkout(OrderProcessor $processor, PaymentMethod $method) { $totalPrice = new Price::free(); foreach ($this->getItems() as $item) { $totalPrice = $totalPrice->add($item->getPrice()); $processor->addItem($item); } $payment = $method->acceptPayment($totalPrice); $processor->setPayment($payment); $processor->pay(); } }
![Page 63: Design how your objects talk through mocking](https://reader035.vdocuments.us/reader035/viewer/2022081412/53fdef1d8d7f72a81c8b4be6/html5/thumbnails/63.jpg)
Final basket checkout
class Basket { // ... public function checkout(OrderProcessor $processor, PaymentMethod $method) { $totalPrice = new Price::free(); foreach ($this->getItems() as $item) { $totalPrice = $totalPrice->add($item->getPrice()); $processor->addItem($item); } $payment = $method->acceptPayment($totalPrice); $processor->setPayment($payment); $processor->pay(); } }
![Page 64: Design how your objects talk through mocking](https://reader035.vdocuments.us/reader035/viewer/2022081412/53fdef1d8d7f72a81c8b4be6/html5/thumbnails/64.jpg)
The 3rd case: browser emulation
![Page 65: Design how your objects talk through mocking](https://reader035.vdocuments.us/reader035/viewer/2022081412/53fdef1d8d7f72a81c8b4be6/html5/thumbnails/65.jpg)
Browser
class Browser { public function __construct(BrowserDriver $driver) { $this->driver = $driver; } public function goto($url) { $this->driver->boot(); $this->driver->visit($url); } }
![Page 66: Design how your objects talk through mocking](https://reader035.vdocuments.us/reader035/viewer/2022081412/53fdef1d8d7f72a81c8b4be6/html5/thumbnails/66.jpg)
Browser drivers
interface BrowserDriver { public function boot(); public function visit($url); } !interface HeadlessBrowserDriver extends BrowserDriver {} !class SeleniumDriver implements BrowserDriver { public function boot() { $this->selenium->startBrowser($this->browser); } ! public function visit($url) { $this->selenium->visitUrl($url); } } !class GuzzleDriver implements HeadlessBrowserDriver { public function boot() {} ! public function visit($url) { $this->guzzle->openUrl($url); } }
![Page 67: Design how your objects talk through mocking](https://reader035.vdocuments.us/reader035/viewer/2022081412/53fdef1d8d7f72a81c8b4be6/html5/thumbnails/67.jpg)
Headless driver test
public function testVisitingProvidedUrl() { $url = 'http://en.wikipedia.org'; $driver = $this->prophesize(HeadlessBrowserDriver::class); ! $driver->visit($url)->shouldBeCalled(); ! $browser = new Browser($driver->reveal()); $browser->goto($url); ! $this->getProphecy()->checkPredictions(); }
![Page 68: Design how your objects talk through mocking](https://reader035.vdocuments.us/reader035/viewer/2022081412/53fdef1d8d7f72a81c8b4be6/html5/thumbnails/68.jpg)
Failing headless driver test
public function testVisitingProvidedUrl() { $url = 'http://en.wikipedia.org'; $driver = $this->prophesize(HeadlessBrowserDriver::class); ! $driver->visit($url)->shouldBeCalled(); ! $browser = new Browser($driver->reveal()); $browser->goto($url); ! $this->getProphecy()->checkPredictions(); }
![Page 69: Design how your objects talk through mocking](https://reader035.vdocuments.us/reader035/viewer/2022081412/53fdef1d8d7f72a81c8b4be6/html5/thumbnails/69.jpg)
Refused Bequest
![Page 70: Design how your objects talk through mocking](https://reader035.vdocuments.us/reader035/viewer/2022081412/53fdef1d8d7f72a81c8b4be6/html5/thumbnails/70.jpg)
Headless driver implementation
class GuzzleDriver implements HeadlessBrowserDriver { ! public function boot() {} ! public function visit($url) { $this->guzzle->openUrl($url); } }
![Page 71: Design how your objects talk through mocking](https://reader035.vdocuments.us/reader035/viewer/2022081412/53fdef1d8d7f72a81c8b4be6/html5/thumbnails/71.jpg)
Headless driver simple behaviour
class GuzzleDriver implements HeadlessBrowserDriver { ! public function boot() {} ! public function visit($url) { $this->guzzle->openUrl($url); } }
![Page 72: Design how your objects talk through mocking](https://reader035.vdocuments.us/reader035/viewer/2022081412/53fdef1d8d7f72a81c8b4be6/html5/thumbnails/72.jpg)
Headless driver that knows about booting
class GuzzleDriver implements HeadlessBrowserDriver { ! public function boot() { $this->allowDoActions = true; } public function visit($url) { if ($this->allowDoActions) $this->guzzle->openUrl($url); } }
![Page 73: Design how your objects talk through mocking](https://reader035.vdocuments.us/reader035/viewer/2022081412/53fdef1d8d7f72a81c8b4be6/html5/thumbnails/73.jpg)
Liskov Substitution Principle
![Page 74: Design how your objects talk through mocking](https://reader035.vdocuments.us/reader035/viewer/2022081412/53fdef1d8d7f72a81c8b4be6/html5/thumbnails/74.jpg)
Adapter layer between BrowserDriver and HeadlessBrowserDriver
interface HeadlessBrowserDriver { public function visit($url); } !class GuzzleDriver implements HeadlessBrowserDriver { public function visit($url) { $this->guzzle->openUrl($url); } } !final class HeadlessBrowserAdapter implements BrowserDriver { private $headlessDriver, $allowDoAction = false; ! public function __construct(HeadlessBrowserDriver $headlessDriver) { $this->headlessDriver = $headlessDriver; } ! public function boot() { $this->allowDoActions = true; } ! public function visit($url) { if ($this->allowDoActions) $this->headlessDriver->visit($url); } }
![Page 75: Design how your objects talk through mocking](https://reader035.vdocuments.us/reader035/viewer/2022081412/53fdef1d8d7f72a81c8b4be6/html5/thumbnails/75.jpg)
Dirty adapter layer between BrowserDriver and HeadlessBrowserDriver
interface HeadlessBrowserDriver { public function visit($url); } !class GuzzleDriver implements HeadlessBrowserDriver { public function visit($url) { $this->guzzle->openUrl($url); } } !final class HeadlessBrowserAdapter implements BrowserDriver { private $headlessDriver, $allowDoAction = false; ! public function __construct(HeadlessBrowserDriver $headlessDriver) { $this->headlessDriver = $headlessDriver; } ! public function boot() { $this->allowDoActions = true; } ! public function visit($url) { if ($this->allowDoActions) $this->headlessDriver->visit($url); } }
![Page 76: Design how your objects talk through mocking](https://reader035.vdocuments.us/reader035/viewer/2022081412/53fdef1d8d7f72a81c8b4be6/html5/thumbnails/76.jpg)
Single adapter layer between BrowserDriver and HeadlessBrowserDriver
interface HeadlessBrowserDriver { public function visit($url); } !class GuzzleDriver implements HeadlessBrowserDriver { public function visit($url) { $this->guzzle->openUrl($url); } } !final class HeadlessBrowserAdapter implements BrowserDriver { private $headlessDriver, $allowDoAction = false; ! public function __construct(HeadlessBrowserDriver $headlessDriver) { $this->headlessDriver = $headlessDriver; } ! public function boot() { $this->allowDoActions = true; } ! public function visit($url) { if ($this->allowDoActions) $this->headlessDriver->visit($url); } }
![Page 77: Design how your objects talk through mocking](https://reader035.vdocuments.us/reader035/viewer/2022081412/53fdef1d8d7f72a81c8b4be6/html5/thumbnails/77.jpg)
The 4th case: ATM screen
![Page 78: Design how your objects talk through mocking](https://reader035.vdocuments.us/reader035/viewer/2022081412/53fdef1d8d7f72a81c8b4be6/html5/thumbnails/78.jpg)
ATM messenger interface
interface Messenger { public function askForCard(); public function askForPin(); public function askForAccount(); public function askForAmount(); public function tellNoMoney(); public function tellMachineEmpty(); }
![Page 79: Design how your objects talk through mocking](https://reader035.vdocuments.us/reader035/viewer/2022081412/53fdef1d8d7f72a81c8b4be6/html5/thumbnails/79.jpg)
City ATM login test
public function testAtmAsksForCardAndPinDuringLogin() { $messenger = $this->prophesize(Messenger::class); ! $messenger->askForCard()->shouldBeCalled(); $messenger->askForPin()->shouldBeCalled(); ! $atm = new CityAtm($messenger->reveal()); $atm->login(); ! $this->getProphet()->checkPredictions(); }
![Page 80: Design how your objects talk through mocking](https://reader035.vdocuments.us/reader035/viewer/2022081412/53fdef1d8d7f72a81c8b4be6/html5/thumbnails/80.jpg)
City ATM login test
public function testAtmAsksForCardAndPinDuringLogin() { $messenger = $this->prophesize(Messenger::class); ! $messenger->askForCard()->shouldBeCalled(); $messenger->askForPin()->shouldBeCalled(); ! $atm = new CityAtm($messenger->reveal()); $atm->login(); ! $this->getProphet()->checkPredictions(); }
![Page 81: Design how your objects talk through mocking](https://reader035.vdocuments.us/reader035/viewer/2022081412/53fdef1d8d7f72a81c8b4be6/html5/thumbnails/81.jpg)
Interface Segregation Principle
![Page 82: Design how your objects talk through mocking](https://reader035.vdocuments.us/reader035/viewer/2022081412/53fdef1d8d7f72a81c8b4be6/html5/thumbnails/82.jpg)
City ATM login test
public function testAtmAsksForCardAndPinDuringLogin() { $messenger = $this->prophesize(LoginMessenger::class); ! $messenger->askForCard()->shouldBeCalled(); $messenger->askForPin()->shouldBeCalled(); ! $atm = new CityAtm($messenger->reveal()); $atm->login(); ! $this->getProphet()->checkPredictions(); }
![Page 83: Design how your objects talk through mocking](https://reader035.vdocuments.us/reader035/viewer/2022081412/53fdef1d8d7f72a81c8b4be6/html5/thumbnails/83.jpg)
ATM messenger interface(s)
interface LoginMessenger { public function askForCard(); public function askForPin(); } !interface InputMessenger { public function askForAccount(); public function askForAmount(); } !interface WithdrawalMessenger { public function tellNoMoney(); public function tellMachineEmpty(); } !interface Messenger extends LoginMessenger, InputMessenger, WithdrawalMessenger
![Page 84: Design how your objects talk through mocking](https://reader035.vdocuments.us/reader035/viewer/2022081412/53fdef1d8d7f72a81c8b4be6/html5/thumbnails/84.jpg)
The 5th case: entity repository
![Page 85: Design how your objects talk through mocking](https://reader035.vdocuments.us/reader035/viewer/2022081412/53fdef1d8d7f72a81c8b4be6/html5/thumbnails/85.jpg)
Doctrine entity repository
class JobRepository extends EntityRepository { public function findJobByName($name) { return $this->findOneBy(['name' => $name]); } }
![Page 86: Design how your objects talk through mocking](https://reader035.vdocuments.us/reader035/viewer/2022081412/53fdef1d8d7f72a81c8b4be6/html5/thumbnails/86.jpg)
Doctrine entity repository test
public function testFindingJobsByName() { $em = $this->prophesize('EntityManager'); $cmd = $this->prophesize('ClassMetadata'); $uow = $this->prophesize('UnitOfWork'); $ep = $this->prophesize('EntityPersister'); $job = Job::fromName('engineer'); ! $em->getUnitOfWork()->willReturn($uow); $uow->getEntityPersister(Argument::any())->willReturn($ep); $ep->load(['name' => 'engineer'], null, null, [], null, 1, null) ->willReturn($job); ! $repo = new JobRepository($em->reveal(), $cmd->reveal()); $actualJob = $repo->findJobByName('engineer'); ! $this->assertSame($job, $actualJob); }
![Page 87: Design how your objects talk through mocking](https://reader035.vdocuments.us/reader035/viewer/2022081412/53fdef1d8d7f72a81c8b4be6/html5/thumbnails/87.jpg)
Doctrine entity repository test
public function testFindingJobsByName() { $em = $this->prophesize('EntityManager'); $cmd = $this->prophesize('ClassMetadata'); $uow = $this->prophesize('UnitOfWork'); $ep = $this->prophesize('EntityPersister'); $job = Job::fromName('engineer'); ! $em->getUnitOfWork()->willReturn($uow); $uow->getEntityPersister(Argument::any())->willReturn($ep); $ep->load(['name' => 'engineer'], null, null, [], null, 1, null) ->willReturn($job); ! $repo = new JobRepository($em->reveal(), $cmd->reveal()); $actualJob = $repo->findJobByName('engineer'); ! $this->assertSame($job, $actualJob); }
![Page 88: Design how your objects talk through mocking](https://reader035.vdocuments.us/reader035/viewer/2022081412/53fdef1d8d7f72a81c8b4be6/html5/thumbnails/88.jpg)
Do not mock things you do not own
![Page 89: Design how your objects talk through mocking](https://reader035.vdocuments.us/reader035/viewer/2022081412/53fdef1d8d7f72a81c8b4be6/html5/thumbnails/89.jpg)
Doctrine entity repository test
public function testFindingJobsByName() { $em = $this->prophesize('EntityManager'); $cmd = $this->prophesize('ClassMetadata'); $uow = $this->prophesize('UnitOfWork'); $ep = $this->prophesize('EntityPersister'); $job = Job::fromName('engineer'); ! $em->getUnitOfWork()->willReturn($uow); $uow->getEntityPersister(Argument::any())->willReturn($ep); $ep->load(['name' => 'engineer'], null, null, [], null, 1, null) ->willReturn($job); ! $repo = new JobRepository($em->reveal(), $cmd->reveal()); $actualJob = $repo->findJobByName('engineer'); ! $this->assertSame($job, $actualJob); }
![Page 90: Design how your objects talk through mocking](https://reader035.vdocuments.us/reader035/viewer/2022081412/53fdef1d8d7f72a81c8b4be6/html5/thumbnails/90.jpg)
Dependency Inversion Principle
![Page 91: Design how your objects talk through mocking](https://reader035.vdocuments.us/reader035/viewer/2022081412/53fdef1d8d7f72a81c8b4be6/html5/thumbnails/91.jpg)
Job repository & Doctrine implementation of it
interface JobRepository { public function findJobByName($name); } !class DoctrineJobRepository extends EntityRepository implements JobRepository { ! public function findJobByName($name) { return $this-‐>findOneBy(['name' => $name]); } }
![Page 92: Design how your objects talk through mocking](https://reader035.vdocuments.us/reader035/viewer/2022081412/53fdef1d8d7f72a81c8b4be6/html5/thumbnails/92.jpg)
Job repository & Doctrine implementation of it
interface JobRepository { public function findJobByName($name); } !class DoctrineJobRepository extends EntityRepository implements JobRepository { ! public function findJobByName($name) { return $this-‐>findOneBy(['name' => $name]); } }
![Page 93: Design how your objects talk through mocking](https://reader035.vdocuments.us/reader035/viewer/2022081412/53fdef1d8d7f72a81c8b4be6/html5/thumbnails/93.jpg)
Job repository & Doctrine implementation of it
interface JobRepository { public function findJobByName($name); } !class DoctrineJobRepository extends EntityRepository implements JobRepository { ! public function findJobByName($name) { return $this-‐>findOneBy(['name' => $name]); } }
![Page 94: Design how your objects talk through mocking](https://reader035.vdocuments.us/reader035/viewer/2022081412/53fdef1d8d7f72a81c8b4be6/html5/thumbnails/94.jpg)
Recap:
![Page 95: Design how your objects talk through mocking](https://reader035.vdocuments.us/reader035/viewer/2022081412/53fdef1d8d7f72a81c8b4be6/html5/thumbnails/95.jpg)
Recap:
1. State-focused TDD is not the only way to TDD
![Page 96: Design how your objects talk through mocking](https://reader035.vdocuments.us/reader035/viewer/2022081412/53fdef1d8d7f72a81c8b4be6/html5/thumbnails/96.jpg)
Recap:
1. State-focused TDD is not the only way to TDD
2. Messaging is far more important concept of OOP than the state
![Page 97: Design how your objects talk through mocking](https://reader035.vdocuments.us/reader035/viewer/2022081412/53fdef1d8d7f72a81c8b4be6/html5/thumbnails/97.jpg)
Recap:
1. State-focused TDD is not the only way to TDD
2. Messaging is far more important concept of OOP than the state
3. By focusing on messaging, you expose messaging problems
![Page 98: Design how your objects talk through mocking](https://reader035.vdocuments.us/reader035/viewer/2022081412/53fdef1d8d7f72a81c8b4be6/html5/thumbnails/98.jpg)
Recap:
1. State-focused TDD is not the only way to TDD
2. Messaging is far more important concept of OOP than the state
3. By focusing on messaging, you expose messaging problems
4. By exposing messaging problems, you could discover most of the SOLID principles violation before they happen
![Page 99: Design how your objects talk through mocking](https://reader035.vdocuments.us/reader035/viewer/2022081412/53fdef1d8d7f72a81c8b4be6/html5/thumbnails/99.jpg)
Recap:
1. State-focused TDD is not the only way to TDD
2. Messaging is far more important concept of OOP than the state
3. By focusing on messaging, you expose messaging problems
4. By exposing messaging problems, you could discover most of the SOLID principles violation before they happen
5. Prophecy is awesome
![Page 100: Design how your objects talk through mocking](https://reader035.vdocuments.us/reader035/viewer/2022081412/53fdef1d8d7f72a81c8b4be6/html5/thumbnails/100.jpg)
6. Messages define objects behaviour
![Page 101: Design how your objects talk through mocking](https://reader035.vdocuments.us/reader035/viewer/2022081412/53fdef1d8d7f72a81c8b4be6/html5/thumbnails/101.jpg)
![Page 102: Design how your objects talk through mocking](https://reader035.vdocuments.us/reader035/viewer/2022081412/53fdef1d8d7f72a81c8b4be6/html5/thumbnails/102.jpg)
![Page 103: Design how your objects talk through mocking](https://reader035.vdocuments.us/reader035/viewer/2022081412/53fdef1d8d7f72a81c8b4be6/html5/thumbnails/103.jpg)
Thank you!