SlideShare a Scribd company logo
1 of 103
Download to read offline
Design how your
objects talk
through mocking
”
– 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.”
@everzet
• BDD Practice Manager

• Software Engineer

• Creator of Behat, Mink,
Prophecy, PhpSpec2

• Contributor to Symfony2,
Doctrine2, Composer
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
Test-driven
development
By Example

!
“The TDD book”

!
Circa 2002
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);
}
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());
}
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('ever.zet@gmail.com'); 
$manager->registerUser($user);
 
$this->assertSame(1, $timesDispatched);
}
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('ever.zet@gmail.com'); 
$manager->registerUser($user);
 
$this->assertSame(1, $timesDispatched);
}
”
– Ralph Waldo Emerson
“Life is a journey, not a destination.”
Growing
Object-Oriented
Software,
Guided by Tests

!
“The GOOS book”

!
Circa 2009
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('ever.zet@gmail.com'); 
$manager->registerUser($user);
 
$this->assertSame(1, $timesDispatched);
}
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('ever.zet@gmail.com'); 
$manager->registerUser($user);
 
$this->assertSame(1, $timesDispatched);
}
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('ever.zet@gmail.com'); 
$manager->registerUser($user);
 
$this->assertSame(1, $timesDispatched);
}
messages or state
”
– 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.”
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();
}
Doubles
1. Dummy

2. Stub

3. Spy

4. Mock

5. Fake
Prophecy
(1) use ProphecyProphet;
(2) use ProphecyArgument;
(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();
1. Dummy
1. Dummy
class System {
private $authorizer;
 
public function __construct(Authorizer $authorizer) {
$this->authorizer = $authorizer;
}
 
public function getLoginCount() {
return 0;
}
}
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());
}
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());
}
2. Stub
2. Stub
class System {
// ...
!
public function logIn($username, $password) {
if ($this->authorizer->authorize($username, $password)) {
$this->loginCount++;
}
}
!
public function getLoginCount() {
return $this->loginCount;
}
}
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());
}
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());
}
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());
}
3. Spy
3. Spy
class System {
// ...
!
public function logIn($username, $password) {
if ($this->authorizer->authorize($username, $password)) {
$this->loginCount++;
$this->lastLoginTimer->recordLogin($username);
}
}
}
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();
}
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();
}
4. Mock
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();
}
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();
}
Back to the
event dispatcher
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('ever.zet@gmail.com'); 
$manager->registerUser($user);
 
$this->assertSame(1, $timesDispatched);
}
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('ever.zet@gmail.com'); 
$manager->registerUser($user);
!
$dispatcher->dispatch('userIsRegistered', Argument::any())
->shouldHaveBeenCalled();
}
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('ever.zet@gmail.com'); 
$manager->registerUser($user);
!
$dispatcher->dispatch('userIsRegistered', Argument::any())
->shouldHaveBeenCalled();
}
Design?
”
– The Observer Effect
“The act of observing will influence the
phenomenon being observed.”
The 1st case:
simple controller
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
]);
}
“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);
}
“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);
}
“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);
}
Single
Responsibility
Principle
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);
}
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);
}
Simpler Symfony2 controller
public function maintainsPackagesAction(User $user) {
$packages = $this->repo->getMaintainedPackagesOrderedByName($user);
!
return $this->tpl->renderResponse('WebBundle:User:packages.html.twig', [
'packages' => $packages
]);
}
The 2nd case:
basket checkout
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();
}
}
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();
}
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();
}
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();
}
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();
}
Open
Closed
Principle
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();
}
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();
}
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();
}
}
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();
}
}
The 3rd case:
browser emulation
Browser
class Browser {
public function __construct(BrowserDriver $driver) {
$this->driver = $driver;
}
 
public function goto($url) {
$this->driver->boot();
$this->driver->visit($url);
}
}
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);
}
}
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();
}
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();
}
Refused Bequest
Headless driver implementation
class GuzzleDriver implements HeadlessBrowserDriver {
!
public function boot() {}
!
public function visit($url) {
$this->guzzle->openUrl($url);
}
}
Headless driver simple behaviour
class GuzzleDriver implements HeadlessBrowserDriver {
!
public function boot() {}
!
public function visit($url) {
$this->guzzle->openUrl($url);
}
}
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);
}
}
Liskov
Substitution
Principle
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);
}
}
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);
}
}
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);
}
}
The 4th case:
ATM screen
ATM messenger interface
interface Messenger {
public function askForCard();
public function askForPin();
public function askForAccount();
public function askForAmount();
public function tellNoMoney();
public function tellMachineEmpty();
}
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();
}
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();
}
Interface
Segregation
Principle
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();
}
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
The 5th case:
entity repository
Doctrine entity repository
class JobRepository extends EntityRepository {
public function findJobByName($name) {
return $this->findOneBy(['name' => $name]);
}
}
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);
}
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);
}
Do not mock things
you do not own
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);
}
Dependency
Inversion
Principle
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]);	
  
	
  	
  	
  	
  }	
  
}
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]);	
  
	
  	
  	
  	
  }	
  
}
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]);	
  
	
  	
  	
  	
  }	
  
}
Recap:
Recap:
1. State-focused TDD is not the only way to TDD
Recap:
1. State-focused TDD is not the only way to TDD

2. Messaging is far more important concept of OOP than
the state
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
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
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
6. Messages define
objects behaviour
Thank you!

More Related Content

What's hot

Decoupling the Ulabox.com monolith. From CRUD to DDD
Decoupling the Ulabox.com monolith. From CRUD to DDDDecoupling the Ulabox.com monolith. From CRUD to DDD
Decoupling the Ulabox.com monolith. From CRUD to DDDAleix Vergés
 
Symfony CoP: Form component
Symfony CoP: Form componentSymfony CoP: Form component
Symfony CoP: Form componentSamuel ROZE
 
Design Patterns avec PHP 5.3, Symfony et Pimple
Design Patterns avec PHP 5.3, Symfony et PimpleDesign Patterns avec PHP 5.3, Symfony et Pimple
Design Patterns avec PHP 5.3, Symfony et PimpleHugo Hamon
 
Symfony & Javascript. Combining the best of two worlds
Symfony & Javascript. Combining the best of two worldsSymfony & Javascript. Combining the best of two worlds
Symfony & Javascript. Combining the best of two worldsIgnacio Martín
 
Min-Maxing Software Costs - Laracon EU 2015
Min-Maxing Software Costs - Laracon EU 2015Min-Maxing Software Costs - Laracon EU 2015
Min-Maxing Software Costs - Laracon EU 2015Konstantin Kudryashov
 
New Symfony Tips & Tricks (SymfonyCon Paris 2015)
New Symfony Tips & Tricks (SymfonyCon Paris 2015)New Symfony Tips & Tricks (SymfonyCon Paris 2015)
New Symfony Tips & Tricks (SymfonyCon Paris 2015)Javier Eguiluz
 
Introduction to CQRS and Event Sourcing
Introduction to CQRS and Event SourcingIntroduction to CQRS and Event Sourcing
Introduction to CQRS and Event SourcingSamuel ROZE
 
How I started to love design patterns
How I started to love design patternsHow I started to love design patterns
How I started to love design patternsSamuel ROZE
 
Doctrine fixtures
Doctrine fixturesDoctrine fixtures
Doctrine fixturesBill Chang
 
Crafting beautiful software
Crafting beautiful softwareCrafting beautiful software
Crafting beautiful softwareJorn Oomen
 
Doctrine For Beginners
Doctrine For BeginnersDoctrine For Beginners
Doctrine For BeginnersJonathan Wage
 
Silex meets SOAP & REST
Silex meets SOAP & RESTSilex meets SOAP & REST
Silex meets SOAP & RESTHugo Hamon
 
Dutch PHP Conference - PHPSpec 2 - The only Design Tool you need
Dutch PHP Conference - PHPSpec 2 - The only Design Tool you needDutch PHP Conference - PHPSpec 2 - The only Design Tool you need
Dutch PHP Conference - PHPSpec 2 - The only Design Tool you needKacper Gunia
 
PHP 5.3 and Lithium: the most rad php framework
PHP 5.3 and Lithium: the most rad php frameworkPHP 5.3 and Lithium: the most rad php framework
PHP 5.3 and Lithium: the most rad php frameworkG Woo
 
The IoC Hydra - Dutch PHP Conference 2016
The IoC Hydra - Dutch PHP Conference 2016The IoC Hydra - Dutch PHP Conference 2016
The IoC Hydra - Dutch PHP Conference 2016Kacper Gunia
 
Database Design Patterns
Database Design PatternsDatabase Design Patterns
Database Design PatternsHugo Hamon
 
The History of PHPersistence
The History of PHPersistenceThe History of PHPersistence
The History of PHPersistenceHugo Hamon
 
The Zen of Lithium
The Zen of LithiumThe Zen of Lithium
The Zen of LithiumNate Abele
 
Speed up your developments with Symfony2
Speed up your developments with Symfony2Speed up your developments with Symfony2
Speed up your developments with Symfony2Hugo Hamon
 

What's hot (20)

Decoupling the Ulabox.com monolith. From CRUD to DDD
Decoupling the Ulabox.com monolith. From CRUD to DDDDecoupling the Ulabox.com monolith. From CRUD to DDD
Decoupling the Ulabox.com monolith. From CRUD to DDD
 
Symfony CoP: Form component
Symfony CoP: Form componentSymfony CoP: Form component
Symfony CoP: Form component
 
Design Patterns avec PHP 5.3, Symfony et Pimple
Design Patterns avec PHP 5.3, Symfony et PimpleDesign Patterns avec PHP 5.3, Symfony et Pimple
Design Patterns avec PHP 5.3, Symfony et Pimple
 
Symfony & Javascript. Combining the best of two worlds
Symfony & Javascript. Combining the best of two worldsSymfony & Javascript. Combining the best of two worlds
Symfony & Javascript. Combining the best of two worlds
 
Min-Maxing Software Costs - Laracon EU 2015
Min-Maxing Software Costs - Laracon EU 2015Min-Maxing Software Costs - Laracon EU 2015
Min-Maxing Software Costs - Laracon EU 2015
 
New Symfony Tips & Tricks (SymfonyCon Paris 2015)
New Symfony Tips & Tricks (SymfonyCon Paris 2015)New Symfony Tips & Tricks (SymfonyCon Paris 2015)
New Symfony Tips & Tricks (SymfonyCon Paris 2015)
 
Introduction to CQRS and Event Sourcing
Introduction to CQRS and Event SourcingIntroduction to CQRS and Event Sourcing
Introduction to CQRS and Event Sourcing
 
How I started to love design patterns
How I started to love design patternsHow I started to love design patterns
How I started to love design patterns
 
Doctrine fixtures
Doctrine fixturesDoctrine fixtures
Doctrine fixtures
 
The IoC Hydra
The IoC HydraThe IoC Hydra
The IoC Hydra
 
Crafting beautiful software
Crafting beautiful softwareCrafting beautiful software
Crafting beautiful software
 
Doctrine For Beginners
Doctrine For BeginnersDoctrine For Beginners
Doctrine For Beginners
 
Silex meets SOAP & REST
Silex meets SOAP & RESTSilex meets SOAP & REST
Silex meets SOAP & REST
 
Dutch PHP Conference - PHPSpec 2 - The only Design Tool you need
Dutch PHP Conference - PHPSpec 2 - The only Design Tool you needDutch PHP Conference - PHPSpec 2 - The only Design Tool you need
Dutch PHP Conference - PHPSpec 2 - The only Design Tool you need
 
PHP 5.3 and Lithium: the most rad php framework
PHP 5.3 and Lithium: the most rad php frameworkPHP 5.3 and Lithium: the most rad php framework
PHP 5.3 and Lithium: the most rad php framework
 
The IoC Hydra - Dutch PHP Conference 2016
The IoC Hydra - Dutch PHP Conference 2016The IoC Hydra - Dutch PHP Conference 2016
The IoC Hydra - Dutch PHP Conference 2016
 
Database Design Patterns
Database Design PatternsDatabase Design Patterns
Database Design Patterns
 
The History of PHPersistence
The History of PHPersistenceThe History of PHPersistence
The History of PHPersistence
 
The Zen of Lithium
The Zen of LithiumThe Zen of Lithium
The Zen of Lithium
 
Speed up your developments with Symfony2
Speed up your developments with Symfony2Speed up your developments with Symfony2
Speed up your developments with Symfony2
 

Viewers also liked

Get Soaked - An In Depth Look At PHP Streams
Get Soaked - An In Depth Look At PHP StreamsGet Soaked - An In Depth Look At PHP Streams
Get Soaked - An In Depth Look At PHP StreamsDavey Shafik
 
Techniques d'accélération des pages web
Techniques d'accélération des pages webTechniques d'accélération des pages web
Techniques d'accélération des pages webJean-Pierre Vincent
 
Automation using-phing
Automation using-phingAutomation using-phing
Automation using-phingRajat Pandit
 
Electrify your code with PHP Generators
Electrify your code with PHP GeneratorsElectrify your code with PHP Generators
Electrify your code with PHP GeneratorsMark Baker
 
The quest for global design principles (SymfonyLive Berlin 2015)
The quest for global design principles (SymfonyLive Berlin 2015)The quest for global design principles (SymfonyLive Berlin 2015)
The quest for global design principles (SymfonyLive Berlin 2015)Matthias Noback
 
Top tips my_sql_performance
Top tips my_sql_performanceTop tips my_sql_performance
Top tips my_sql_performanceafup Paris
 
Why elasticsearch rocks!
Why elasticsearch rocks!Why elasticsearch rocks!
Why elasticsearch rocks!tlrx
 
Understanding Craftsmanship SwanseaCon2015
Understanding Craftsmanship SwanseaCon2015Understanding Craftsmanship SwanseaCon2015
Understanding Craftsmanship SwanseaCon2015Marcello Duarte
 
Si le tdd est mort alors pratiquons une autopsie mix-it 2015
Si le tdd est mort alors pratiquons une autopsie mix-it 2015Si le tdd est mort alors pratiquons une autopsie mix-it 2015
Si le tdd est mort alors pratiquons une autopsie mix-it 2015Bruno Boucard
 
Writing infinite scalability web applications with PHP and PostgreSQL
Writing infinite scalability web applications with PHP and PostgreSQLWriting infinite scalability web applications with PHP and PostgreSQL
Writing infinite scalability web applications with PHP and PostgreSQLGabriele Bartolini
 
L'ABC du BDD (Behavior Driven Development)
L'ABC du BDD (Behavior Driven Development)L'ABC du BDD (Behavior Driven Development)
L'ABC du BDD (Behavior Driven Development)Arnauld Loyer
 
Performance serveur et apache
Performance serveur et apachePerformance serveur et apache
Performance serveur et apacheafup Paris
 
TDD with PhpSpec - Lone Star PHP 2016
TDD with PhpSpec - Lone Star PHP 2016TDD with PhpSpec - Lone Star PHP 2016
TDD with PhpSpec - Lone Star PHP 2016CiaranMcNulty
 
The Wonderful World of Symfony Components
The Wonderful World of Symfony ComponentsThe Wonderful World of Symfony Components
The Wonderful World of Symfony ComponentsRyan Weaver
 

Viewers also liked (20)

Elastic Searching With PHP
Elastic Searching With PHPElastic Searching With PHP
Elastic Searching With PHP
 
Diving deep into twig
Diving deep into twigDiving deep into twig
Diving deep into twig
 
Get Soaked - An In Depth Look At PHP Streams
Get Soaked - An In Depth Look At PHP StreamsGet Soaked - An In Depth Look At PHP Streams
Get Soaked - An In Depth Look At PHP Streams
 
Techniques d'accélération des pages web
Techniques d'accélération des pages webTechniques d'accélération des pages web
Techniques d'accélération des pages web
 
Automation using-phing
Automation using-phingAutomation using-phing
Automation using-phing
 
PHP5.5 is Here
PHP5.5 is HerePHP5.5 is Here
PHP5.5 is Here
 
Electrify your code with PHP Generators
Electrify your code with PHP GeneratorsElectrify your code with PHP Generators
Electrify your code with PHP Generators
 
The quest for global design principles (SymfonyLive Berlin 2015)
The quest for global design principles (SymfonyLive Berlin 2015)The quest for global design principles (SymfonyLive Berlin 2015)
The quest for global design principles (SymfonyLive Berlin 2015)
 
Top tips my_sql_performance
Top tips my_sql_performanceTop tips my_sql_performance
Top tips my_sql_performance
 
Mocking Demystified
Mocking DemystifiedMocking Demystified
Mocking Demystified
 
Why elasticsearch rocks!
Why elasticsearch rocks!Why elasticsearch rocks!
Why elasticsearch rocks!
 
Understanding Craftsmanship SwanseaCon2015
Understanding Craftsmanship SwanseaCon2015Understanding Craftsmanship SwanseaCon2015
Understanding Craftsmanship SwanseaCon2015
 
Si le tdd est mort alors pratiquons une autopsie mix-it 2015
Si le tdd est mort alors pratiquons une autopsie mix-it 2015Si le tdd est mort alors pratiquons une autopsie mix-it 2015
Si le tdd est mort alors pratiquons une autopsie mix-it 2015
 
Writing infinite scalability web applications with PHP and PostgreSQL
Writing infinite scalability web applications with PHP and PostgreSQLWriting infinite scalability web applications with PHP and PostgreSQL
Writing infinite scalability web applications with PHP and PostgreSQL
 
L'ABC du BDD (Behavior Driven Development)
L'ABC du BDD (Behavior Driven Development)L'ABC du BDD (Behavior Driven Development)
L'ABC du BDD (Behavior Driven Development)
 
Performance serveur et apache
Performance serveur et apachePerformance serveur et apache
Performance serveur et apache
 
Behat 3.0 meetup (March)
Behat 3.0 meetup (March)Behat 3.0 meetup (March)
Behat 3.0 meetup (March)
 
Caching on the Edge
Caching on the EdgeCaching on the Edge
Caching on the Edge
 
TDD with PhpSpec - Lone Star PHP 2016
TDD with PhpSpec - Lone Star PHP 2016TDD with PhpSpec - Lone Star PHP 2016
TDD with PhpSpec - Lone Star PHP 2016
 
The Wonderful World of Symfony Components
The Wonderful World of Symfony ComponentsThe Wonderful World of Symfony Components
The Wonderful World of Symfony Components
 

Similar to Design how objects communicate through mocking

Advanced php testing in action
Advanced php testing in actionAdvanced php testing in action
Advanced php testing in actionJace Ju
 
Mocking Dependencies in PHPUnit
Mocking Dependencies in PHPUnitMocking Dependencies in PHPUnit
Mocking Dependencies in PHPUnitmfrost503
 
Unit testing with zend framework tek11
Unit testing with zend framework tek11Unit testing with zend framework tek11
Unit testing with zend framework tek11Michelangelo van Dam
 
Unit testing with zend framework PHPBenelux
Unit testing with zend framework PHPBeneluxUnit testing with zend framework PHPBenelux
Unit testing with zend framework PHPBeneluxMichelangelo van Dam
 
PHPSpec - the only Design Tool you need - 4Developers
PHPSpec - the only Design Tool you need - 4DevelopersPHPSpec - the only Design Tool you need - 4Developers
PHPSpec - the only Design Tool you need - 4DevelopersKacper Gunia
 
Mocking Dependencies in PHPUnit
Mocking Dependencies in PHPUnitMocking Dependencies in PHPUnit
Mocking Dependencies in PHPUnitmfrost503
 
Как получить чёрный пояс по WordPress?
Как получить чёрный пояс по WordPress?Как получить чёрный пояс по WordPress?
Как получить чёрный пояс по WordPress?Yevhen Kotelnytskyi
 
Your code sucks, let's fix it - DPC UnCon
Your code sucks, let's fix it - DPC UnConYour code sucks, let's fix it - DPC UnCon
Your code sucks, let's fix it - DPC UnConRafael Dohms
 
You code sucks, let's fix it
You code sucks, let's fix itYou code sucks, let's fix it
You code sucks, let's fix itRafael Dohms
 
EPHPC Webinar Slides: Unit Testing by Arthur Purnama
EPHPC Webinar Slides: Unit Testing by Arthur PurnamaEPHPC Webinar Slides: Unit Testing by Arthur Purnama
EPHPC Webinar Slides: Unit Testing by Arthur PurnamaEnterprise PHP Center
 
Как получить чёрный пояс по WordPress? v2.0
Как получить чёрный пояс по WordPress? v2.0Как получить чёрный пояс по WordPress? v2.0
Как получить чёрный пояс по WordPress? v2.0Yevhen Kotelnytskyi
 
WordPress as an application framework
WordPress as an application frameworkWordPress as an application framework
WordPress as an application frameworkDustin Filippini
 
Quality assurance for php projects with PHPStorm
Quality assurance for php projects with PHPStormQuality assurance for php projects with PHPStorm
Quality assurance for php projects with PHPStormMichelangelo van Dam
 
Command-Oriented Architecture
Command-Oriented ArchitectureCommand-Oriented Architecture
Command-Oriented ArchitectureLuiz Messias
 
Unit testing after Zend Framework 1.8
Unit testing after Zend Framework 1.8Unit testing after Zend Framework 1.8
Unit testing after Zend Framework 1.8Michelangelo van Dam
 
Load Testing with PHP and RedLine13
Load Testing with PHP and RedLine13Load Testing with PHP and RedLine13
Load Testing with PHP and RedLine13Jason Lotito
 

Similar to Design how objects communicate through mocking (20)

Advanced php testing in action
Advanced php testing in actionAdvanced php testing in action
Advanced php testing in action
 
Unit testing zend framework apps
Unit testing zend framework appsUnit testing zend framework apps
Unit testing zend framework apps
 
Mocking Dependencies in PHPUnit
Mocking Dependencies in PHPUnitMocking Dependencies in PHPUnit
Mocking Dependencies in PHPUnit
 
Unit testing with zend framework tek11
Unit testing with zend framework tek11Unit testing with zend framework tek11
Unit testing with zend framework tek11
 
Unit testing with zend framework PHPBenelux
Unit testing with zend framework PHPBeneluxUnit testing with zend framework PHPBenelux
Unit testing with zend framework PHPBenelux
 
PHPSpec - the only Design Tool you need - 4Developers
PHPSpec - the only Design Tool you need - 4DevelopersPHPSpec - the only Design Tool you need - 4Developers
PHPSpec - the only Design Tool you need - 4Developers
 
Mocking Dependencies in PHPUnit
Mocking Dependencies in PHPUnitMocking Dependencies in PHPUnit
Mocking Dependencies in PHPUnit
 
Как получить чёрный пояс по WordPress?
Как получить чёрный пояс по WordPress?Как получить чёрный пояс по WordPress?
Как получить чёрный пояс по WordPress?
 
Your code sucks, let's fix it - DPC UnCon
Your code sucks, let's fix it - DPC UnConYour code sucks, let's fix it - DPC UnCon
Your code sucks, let's fix it - DPC UnCon
 
You code sucks, let's fix it
You code sucks, let's fix itYou code sucks, let's fix it
You code sucks, let's fix it
 
EPHPC Webinar Slides: Unit Testing by Arthur Purnama
EPHPC Webinar Slides: Unit Testing by Arthur PurnamaEPHPC Webinar Slides: Unit Testing by Arthur Purnama
EPHPC Webinar Slides: Unit Testing by Arthur Purnama
 
Как получить чёрный пояс по WordPress? v2.0
Как получить чёрный пояс по WordPress? v2.0Как получить чёрный пояс по WordPress? v2.0
Как получить чёрный пояс по WordPress? v2.0
 
Zend framework service
Zend framework serviceZend framework service
Zend framework service
 
Zend framework service
Zend framework serviceZend framework service
Zend framework service
 
PHP Unit Testing
PHP Unit TestingPHP Unit Testing
PHP Unit Testing
 
WordPress as an application framework
WordPress as an application frameworkWordPress as an application framework
WordPress as an application framework
 
Quality assurance for php projects with PHPStorm
Quality assurance for php projects with PHPStormQuality assurance for php projects with PHPStorm
Quality assurance for php projects with PHPStorm
 
Command-Oriented Architecture
Command-Oriented ArchitectureCommand-Oriented Architecture
Command-Oriented Architecture
 
Unit testing after Zend Framework 1.8
Unit testing after Zend Framework 1.8Unit testing after Zend Framework 1.8
Unit testing after Zend Framework 1.8
 
Load Testing with PHP and RedLine13
Load Testing with PHP and RedLine13Load Testing with PHP and RedLine13
Load Testing with PHP and RedLine13
 

More from Konstantin Kudryashov

Being effective with legacy projects
Being effective with legacy projectsBeing effective with legacy projects
Being effective with legacy projectsKonstantin Kudryashov
 
Bridging The Communication Gap, Fast
Bridging The Communication Gap, Fast Bridging The Communication Gap, Fast
Bridging The Communication Gap, Fast Konstantin Kudryashov
 
Moving away from legacy code (AgileCymru)
Moving away from legacy code  (AgileCymru)Moving away from legacy code  (AgileCymru)
Moving away from legacy code (AgileCymru)Konstantin Kudryashov
 
Moving away from legacy code with BDD
Moving away from legacy code with BDDMoving away from legacy code with BDD
Moving away from legacy code with BDDKonstantin Kudryashov
 
Enabling agile devliery through enabling BDD in PHP projects
Enabling agile devliery through enabling BDD in PHP projectsEnabling agile devliery through enabling BDD in PHP projects
Enabling agile devliery through enabling BDD in PHP projectsKonstantin Kudryashov
 
Moving away from legacy code with BDD
Moving away from legacy code with BDDMoving away from legacy code with BDD
Moving away from legacy code with BDDKonstantin Kudryashov
 
LESS, SASS, HAML: 4 буквы, изменившие frontend development
LESS, SASS, HAML: 4 буквы, изменившие frontend developmentLESS, SASS, HAML: 4 буквы, изменившие frontend development
LESS, SASS, HAML: 4 буквы, изменившие frontend developmentKonstantin Kudryashov
 
Автоматизируем деплоймент проекта с помощью Capistrano
Автоматизируем деплоймент проекта с помощью CapistranoАвтоматизируем деплоймент проекта с помощью Capistrano
Автоматизируем деплоймент проекта с помощью CapistranoKonstantin Kudryashov
 

More from Konstantin Kudryashov (14)

Modern Agile Project Toolbox
Modern Agile Project ToolboxModern Agile Project Toolbox
Modern Agile Project Toolbox
 
Being effective with legacy projects
Being effective with legacy projectsBeing effective with legacy projects
Being effective with legacy projects
 
Modern Project Toolbox
Modern Project ToolboxModern Project Toolbox
Modern Project Toolbox
 
Bridging The Communication Gap, Fast
Bridging The Communication Gap, Fast Bridging The Communication Gap, Fast
Bridging The Communication Gap, Fast
 
Moving away from legacy code (AgileCymru)
Moving away from legacy code  (AgileCymru)Moving away from legacy code  (AgileCymru)
Moving away from legacy code (AgileCymru)
 
Taking back BDD
Taking back BDDTaking back BDD
Taking back BDD
 
Moving away from legacy code with BDD
Moving away from legacy code with BDDMoving away from legacy code with BDD
Moving away from legacy code with BDD
 
Enabling agile devliery through enabling BDD in PHP projects
Enabling agile devliery through enabling BDD in PHP projectsEnabling agile devliery through enabling BDD in PHP projects
Enabling agile devliery through enabling BDD in PHP projects
 
Moving away from legacy code with BDD
Moving away from legacy code with BDDMoving away from legacy code with BDD
Moving away from legacy code with BDD
 
BDD в PHP с Behat и Mink
BDD в PHP с Behat и MinkBDD в PHP с Behat и Mink
BDD в PHP с Behat и Mink
 
BDD in Symfony2
BDD in Symfony2BDD in Symfony2
BDD in Symfony2
 
BDD для PHP проектов
BDD для PHP проектовBDD для PHP проектов
BDD для PHP проектов
 
LESS, SASS, HAML: 4 буквы, изменившие frontend development
LESS, SASS, HAML: 4 буквы, изменившие frontend developmentLESS, SASS, HAML: 4 буквы, изменившие frontend development
LESS, SASS, HAML: 4 буквы, изменившие frontend development
 
Автоматизируем деплоймент проекта с помощью Capistrano
Автоматизируем деплоймент проекта с помощью CapistranoАвтоматизируем деплоймент проекта с помощью Capistrano
Автоматизируем деплоймент проекта с помощью Capistrano
 

Recently uploaded

Cloud Data Center Network Construction - IEEE
Cloud Data Center Network Construction - IEEECloud Data Center Network Construction - IEEE
Cloud Data Center Network Construction - IEEEVICTOR MAESTRE RAMIREZ
 
PREDICTING RIVER WATER QUALITY ppt presentation
PREDICTING  RIVER  WATER QUALITY  ppt presentationPREDICTING  RIVER  WATER QUALITY  ppt presentation
PREDICTING RIVER WATER QUALITY ppt presentationvaddepallysandeep122
 
Software Project Health Check: Best Practices and Techniques for Your Product...
Software Project Health Check: Best Practices and Techniques for Your Product...Software Project Health Check: Best Practices and Techniques for Your Product...
Software Project Health Check: Best Practices and Techniques for Your Product...Velvetech LLC
 
Open Source Summit NA 2024: Open Source Cloud Costs - OpenCost's Impact on En...
Open Source Summit NA 2024: Open Source Cloud Costs - OpenCost's Impact on En...Open Source Summit NA 2024: Open Source Cloud Costs - OpenCost's Impact on En...
Open Source Summit NA 2024: Open Source Cloud Costs - OpenCost's Impact on En...Matt Ray
 
Tech Tuesday - Mastering Time Management Unlock the Power of OnePlan's Timesh...
Tech Tuesday - Mastering Time Management Unlock the Power of OnePlan's Timesh...Tech Tuesday - Mastering Time Management Unlock the Power of OnePlan's Timesh...
Tech Tuesday - Mastering Time Management Unlock the Power of OnePlan's Timesh...OnePlan Solutions
 
Global Identity Enrolment and Verification Pro Solution - Cizo Technology Ser...
Global Identity Enrolment and Verification Pro Solution - Cizo Technology Ser...Global Identity Enrolment and Verification Pro Solution - Cizo Technology Ser...
Global Identity Enrolment and Verification Pro Solution - Cizo Technology Ser...Cizo Technology Services
 
A healthy diet for your Java application Devoxx France.pdf
A healthy diet for your Java application Devoxx France.pdfA healthy diet for your Java application Devoxx France.pdf
A healthy diet for your Java application Devoxx France.pdfMarharyta Nedzelska
 
Automate your Kamailio Test Calls - Kamailio World 2024
Automate your Kamailio Test Calls - Kamailio World 2024Automate your Kamailio Test Calls - Kamailio World 2024
Automate your Kamailio Test Calls - Kamailio World 2024Andreas Granig
 
VK Business Profile - provides IT solutions and Web Development
VK Business Profile - provides IT solutions and Web DevelopmentVK Business Profile - provides IT solutions and Web Development
VK Business Profile - provides IT solutions and Web Developmentvyaparkranti
 
Precise and Complete Requirements? An Elusive Goal
Precise and Complete Requirements? An Elusive GoalPrecise and Complete Requirements? An Elusive Goal
Precise and Complete Requirements? An Elusive GoalLionel Briand
 
Sending Calendar Invites on SES and Calendarsnack.pdf
Sending Calendar Invites on SES and Calendarsnack.pdfSending Calendar Invites on SES and Calendarsnack.pdf
Sending Calendar Invites on SES and Calendarsnack.pdf31events.com
 
SensoDat: Simulation-based Sensor Dataset of Self-driving Cars
SensoDat: Simulation-based Sensor Dataset of Self-driving CarsSensoDat: Simulation-based Sensor Dataset of Self-driving Cars
SensoDat: Simulation-based Sensor Dataset of Self-driving CarsChristian Birchler
 
SpotFlow: Tracking Method Calls and States at Runtime
SpotFlow: Tracking Method Calls and States at RuntimeSpotFlow: Tracking Method Calls and States at Runtime
SpotFlow: Tracking Method Calls and States at Runtimeandrehoraa
 
How To Manage Restaurant Staff -BTRESTRO
How To Manage Restaurant Staff -BTRESTROHow To Manage Restaurant Staff -BTRESTRO
How To Manage Restaurant Staff -BTRESTROmotivationalword821
 
CRM Contender Series: HubSpot vs. Salesforce
CRM Contender Series: HubSpot vs. SalesforceCRM Contender Series: HubSpot vs. Salesforce
CRM Contender Series: HubSpot vs. SalesforceBrainSell Technologies
 
Salesforce Implementation Services PPT By ABSYZ
Salesforce Implementation Services PPT By ABSYZSalesforce Implementation Services PPT By ABSYZ
Salesforce Implementation Services PPT By ABSYZABSYZ Inc
 
How to submit a standout Adobe Champion Application
How to submit a standout Adobe Champion ApplicationHow to submit a standout Adobe Champion Application
How to submit a standout Adobe Champion ApplicationBradBedford3
 
Ahmed Motair CV April 2024 (Senior SW Developer)
Ahmed Motair CV April 2024 (Senior SW Developer)Ahmed Motair CV April 2024 (Senior SW Developer)
Ahmed Motair CV April 2024 (Senior SW Developer)Ahmed Mater
 
Folding Cheat Sheet #4 - fourth in a series
Folding Cheat Sheet #4 - fourth in a seriesFolding Cheat Sheet #4 - fourth in a series
Folding Cheat Sheet #4 - fourth in a seriesPhilip Schwarz
 
Catch the Wave: SAP Event-Driven and Data Streaming for the Intelligence Ente...
Catch the Wave: SAP Event-Driven and Data Streaming for the Intelligence Ente...Catch the Wave: SAP Event-Driven and Data Streaming for the Intelligence Ente...
Catch the Wave: SAP Event-Driven and Data Streaming for the Intelligence Ente...confluent
 

Recently uploaded (20)

Cloud Data Center Network Construction - IEEE
Cloud Data Center Network Construction - IEEECloud Data Center Network Construction - IEEE
Cloud Data Center Network Construction - IEEE
 
PREDICTING RIVER WATER QUALITY ppt presentation
PREDICTING  RIVER  WATER QUALITY  ppt presentationPREDICTING  RIVER  WATER QUALITY  ppt presentation
PREDICTING RIVER WATER QUALITY ppt presentation
 
Software Project Health Check: Best Practices and Techniques for Your Product...
Software Project Health Check: Best Practices and Techniques for Your Product...Software Project Health Check: Best Practices and Techniques for Your Product...
Software Project Health Check: Best Practices and Techniques for Your Product...
 
Open Source Summit NA 2024: Open Source Cloud Costs - OpenCost's Impact on En...
Open Source Summit NA 2024: Open Source Cloud Costs - OpenCost's Impact on En...Open Source Summit NA 2024: Open Source Cloud Costs - OpenCost's Impact on En...
Open Source Summit NA 2024: Open Source Cloud Costs - OpenCost's Impact on En...
 
Tech Tuesday - Mastering Time Management Unlock the Power of OnePlan's Timesh...
Tech Tuesday - Mastering Time Management Unlock the Power of OnePlan's Timesh...Tech Tuesday - Mastering Time Management Unlock the Power of OnePlan's Timesh...
Tech Tuesday - Mastering Time Management Unlock the Power of OnePlan's Timesh...
 
Global Identity Enrolment and Verification Pro Solution - Cizo Technology Ser...
Global Identity Enrolment and Verification Pro Solution - Cizo Technology Ser...Global Identity Enrolment and Verification Pro Solution - Cizo Technology Ser...
Global Identity Enrolment and Verification Pro Solution - Cizo Technology Ser...
 
A healthy diet for your Java application Devoxx France.pdf
A healthy diet for your Java application Devoxx France.pdfA healthy diet for your Java application Devoxx France.pdf
A healthy diet for your Java application Devoxx France.pdf
 
Automate your Kamailio Test Calls - Kamailio World 2024
Automate your Kamailio Test Calls - Kamailio World 2024Automate your Kamailio Test Calls - Kamailio World 2024
Automate your Kamailio Test Calls - Kamailio World 2024
 
VK Business Profile - provides IT solutions and Web Development
VK Business Profile - provides IT solutions and Web DevelopmentVK Business Profile - provides IT solutions and Web Development
VK Business Profile - provides IT solutions and Web Development
 
Precise and Complete Requirements? An Elusive Goal
Precise and Complete Requirements? An Elusive GoalPrecise and Complete Requirements? An Elusive Goal
Precise and Complete Requirements? An Elusive Goal
 
Sending Calendar Invites on SES and Calendarsnack.pdf
Sending Calendar Invites on SES and Calendarsnack.pdfSending Calendar Invites on SES and Calendarsnack.pdf
Sending Calendar Invites on SES and Calendarsnack.pdf
 
SensoDat: Simulation-based Sensor Dataset of Self-driving Cars
SensoDat: Simulation-based Sensor Dataset of Self-driving CarsSensoDat: Simulation-based Sensor Dataset of Self-driving Cars
SensoDat: Simulation-based Sensor Dataset of Self-driving Cars
 
SpotFlow: Tracking Method Calls and States at Runtime
SpotFlow: Tracking Method Calls and States at RuntimeSpotFlow: Tracking Method Calls and States at Runtime
SpotFlow: Tracking Method Calls and States at Runtime
 
How To Manage Restaurant Staff -BTRESTRO
How To Manage Restaurant Staff -BTRESTROHow To Manage Restaurant Staff -BTRESTRO
How To Manage Restaurant Staff -BTRESTRO
 
CRM Contender Series: HubSpot vs. Salesforce
CRM Contender Series: HubSpot vs. SalesforceCRM Contender Series: HubSpot vs. Salesforce
CRM Contender Series: HubSpot vs. Salesforce
 
Salesforce Implementation Services PPT By ABSYZ
Salesforce Implementation Services PPT By ABSYZSalesforce Implementation Services PPT By ABSYZ
Salesforce Implementation Services PPT By ABSYZ
 
How to submit a standout Adobe Champion Application
How to submit a standout Adobe Champion ApplicationHow to submit a standout Adobe Champion Application
How to submit a standout Adobe Champion Application
 
Ahmed Motair CV April 2024 (Senior SW Developer)
Ahmed Motair CV April 2024 (Senior SW Developer)Ahmed Motair CV April 2024 (Senior SW Developer)
Ahmed Motair CV April 2024 (Senior SW Developer)
 
Folding Cheat Sheet #4 - fourth in a series
Folding Cheat Sheet #4 - fourth in a seriesFolding Cheat Sheet #4 - fourth in a series
Folding Cheat Sheet #4 - fourth in a series
 
Catch the Wave: SAP Event-Driven and Data Streaming for the Intelligence Ente...
Catch the Wave: SAP Event-Driven and Data Streaming for the Intelligence Ente...Catch the Wave: SAP Event-Driven and Data Streaming for the Intelligence Ente...
Catch the Wave: SAP Event-Driven and Data Streaming for the Intelligence Ente...
 

Design how objects communicate through mocking

  • 1. Design how your objects talk through mocking
  • 2. ” – 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.”
  • 3. @everzet • BDD Practice Manager • Software Engineer • Creator of Behat, Mink, Prophecy, PhpSpec2 • Contributor to Symfony2, Doctrine2, Composer
  • 4. 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
  • 6. 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); }
  • 7. 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()); }
  • 8. 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('ever.zet@gmail.com');  $manager->registerUser($user);   $this->assertSame(1, $timesDispatched); }
  • 9. 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('ever.zet@gmail.com');  $manager->registerUser($user);   $this->assertSame(1, $timesDispatched); }
  • 10.
  • 11.
  • 12. ” – Ralph Waldo Emerson “Life is a journey, not a destination.”
  • 14. 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('ever.zet@gmail.com');  $manager->registerUser($user);   $this->assertSame(1, $timesDispatched); }
  • 15. 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('ever.zet@gmail.com');  $manager->registerUser($user);   $this->assertSame(1, $timesDispatched); }
  • 16. 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('ever.zet@gmail.com');  $manager->registerUser($user);   $this->assertSame(1, $timesDispatched); }
  • 18. ” – 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.”
  • 19. 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(); }
  • 20. Doubles 1. Dummy 2. Stub 3. Spy 4. Mock 5. Fake
  • 21. Prophecy (1) use ProphecyProphet; (2) use ProphecyArgument; (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();
  • 23. 1. Dummy class System { private $authorizer;   public function __construct(Authorizer $authorizer) { $this->authorizer = $authorizer; }   public function getLoginCount() { return 0; } }
  • 24. 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()); }
  • 25. 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()); }
  • 27. 2. Stub class System { // ... ! public function logIn($username, $password) { if ($this->authorizer->authorize($username, $password)) { $this->loginCount++; } } ! public function getLoginCount() { return $this->loginCount; } }
  • 28. 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()); }
  • 29. 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()); }
  • 30. 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()); }
  • 32. 3. Spy class System { // ... ! public function logIn($username, $password) { if ($this->authorizer->authorize($username, $password)) { $this->loginCount++; $this->lastLoginTimer->recordLogin($username); } } }
  • 33. 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(); }
  • 34. 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(); }
  • 36. 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(); }
  • 37. 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(); }
  • 38. Back to the event dispatcher
  • 39. 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('ever.zet@gmail.com');  $manager->registerUser($user);   $this->assertSame(1, $timesDispatched); }
  • 40. 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('ever.zet@gmail.com');  $manager->registerUser($user); ! $dispatcher->dispatch('userIsRegistered', Argument::any()) ->shouldHaveBeenCalled(); }
  • 41. 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('ever.zet@gmail.com');  $manager->registerUser($user); ! $dispatcher->dispatch('userIsRegistered', Argument::any()) ->shouldHaveBeenCalled(); }
  • 43. ” – The Observer Effect “The act of observing will influence the phenomenon being observed.”
  • 44. The 1st case: simple controller
  • 45. 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 ]); }
  • 46. “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); }
  • 47. “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); }
  • 48. “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); }
  • 50. 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); }
  • 51. 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); }
  • 52. Simpler Symfony2 controller public function maintainsPackagesAction(User $user) { $packages = $this->repo->getMaintainedPackagesOrderedByName($user); ! return $this->tpl->renderResponse('WebBundle:User:packages.html.twig', [ 'packages' => $packages ]); }
  • 54. 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(); } }
  • 55. 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(); }
  • 56. 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(); }
  • 57. 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(); }
  • 58. 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(); }
  • 60. 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(); }
  • 61. 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(); }
  • 62. 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(); } }
  • 63. 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(); } }
  • 65. Browser class Browser { public function __construct(BrowserDriver $driver) { $this->driver = $driver; }   public function goto($url) { $this->driver->boot(); $this->driver->visit($url); } }
  • 66. 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); } }
  • 67. 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(); }
  • 68. 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(); }
  • 70. Headless driver implementation class GuzzleDriver implements HeadlessBrowserDriver { ! public function boot() {} ! public function visit($url) { $this->guzzle->openUrl($url); } }
  • 71. Headless driver simple behaviour class GuzzleDriver implements HeadlessBrowserDriver { ! public function boot() {} ! public function visit($url) { $this->guzzle->openUrl($url); } }
  • 72. 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); } }
  • 74. 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); } }
  • 75. 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); } }
  • 76. 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); } }
  • 78. ATM messenger interface interface Messenger { public function askForCard(); public function askForPin(); public function askForAccount(); public function askForAmount(); public function tellNoMoney(); public function tellMachineEmpty(); }
  • 79. 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(); }
  • 80. 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(); }
  • 82. 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(); }
  • 83. 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
  • 84. The 5th case: entity repository
  • 85. Doctrine entity repository class JobRepository extends EntityRepository { public function findJobByName($name) { return $this->findOneBy(['name' => $name]); } }
  • 86. 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); }
  • 87. 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); }
  • 88. Do not mock things you do not own
  • 89. 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); }
  • 91. 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]);          }   }
  • 92. 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]);          }   }
  • 93. 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]);          }   }
  • 95. Recap: 1. State-focused TDD is not the only way to TDD
  • 96. Recap: 1. State-focused TDD is not the only way to TDD 2. Messaging is far more important concept of OOP than the state
  • 97. 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
  • 98. 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
  • 99. 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
  • 101.
  • 102.