Slide 1

Slide 1 text

PHP OBJECT MOCKING FRAMEWORK WORLD LET'S COMPARE PROPHECY AND PHPUNIT Sarah Khalil - @saro0h

Slide 2

Slide 2 text

TODAY, HOPEFULLY, WE LEARN NEW THINGS 1. Terminology about objects doubling 2. PHPUnit implementation 3. Prophecy implementation 4. The differences between the two philosophies

Slide 3

Slide 3 text

TERMINOLOGY

Slide 4

Slide 4 text

DUMMIES

Slide 5

Slide 5 text

Dummies are objects that are passed around but never used. They are usually used to fill a list of parameters.

Slide 6

Slide 6 text

STUB

Slide 7

Slide 7 text

Stubs are objects that implement the same methods than the real object. These methods do nothing and are configured to return a specific value.

Slide 8

Slide 8 text

MOCK

Slide 9

Slide 9 text

Mocks are pre-programmed with expectations which form a specification of the calls they are expected to receive. They can throw an exception if they receive a call they don't expect and are checked during verification to ensure they got all the calls they were expecting.

Slide 10

Slide 10 text

PHPUNIT MOCKING LIBRARY

Slide 11

Slide 11 text

INSTALL IT

Slide 12

Slide 12 text

FUNDAMENTALS

Slide 13

Slide 13 text

Your test class must extend PHPUnit_Framework_TestCase

Slide 14

Slide 14 text

The method you need to know $this->getMock()

Slide 15

Slide 15 text

• Generates an object • All methods return NULL • You can describe the expected behavior of your object

Slide 16

Slide 16 text

DUMMY $dummy = $this->getMock('Namespace');

Slide 17

Slide 17 text

STUB $event = $this->getMock(‘Symfony\Component\HttpKernel\Event \GetResponseEvent’); ! $event ->method('getRequest') ->will($this->returnValue($this->request)) ;

Slide 18

Slide 18 text

MOCK $dispatcher = $this->getMock(‘Symfony\Component \EventDispatcher\EventDispatcherInterface'); ! $dispatcher ->expects($this->once()) ->method(‘dispatch') ->with( $this->equalTo(SecurityEvents::INTERACTIVE_LOGIN), $this->equalTo($loginEvent) ) ;

Slide 19

Slide 19 text

REAL LIFE EXAMPLE

Slide 20

Slide 20 text

No content

Slide 21

Slide 21 text

No content

Slide 22

Slide 22 text

Who knows exactly how the Oauth dance of Github works? Don’t worry, we don’t care!

Slide 23

Slide 23 text

THE ORIGINAL CODE

Slide 24

Slide 24 text

namespace PoleDev\AppBundle\Security;! ! use Guzzle\Service\Client;! use Psr\Log\LoggerInterface;! use Symfony\[…]\Router;! use Symfony\[…]\Response;! use Symfony\[…]\Request;! use Symfony\[…]\SimplePreAuthenticatorInterface;! use Symfony\[…]\AuthenticationFailureHandlerInterface;! use Symfony\[…]\TokenInterface;! use Symfony\[…]\UserProviderInterface;! use Symfony\[…]\AuthenticationException;! use Symfony\[…]\UrlGeneratorInterface;! use Symfony\[…]\HttpException;! use Symfony\[…]\PreAuthenticatedToken;! ! class GithubAuthenticator implements SimplePreAuthenticatorInterface, AuthenticationFailureHandlerInterface! {! ! // Some code…! }

Slide 25

Slide 25 text

private $client; private $router; private $logger; ! public function __construct( Client $client, Router $router, LoggerInterface $logger, $clientId, $clientSecret ) { $this->client = $client; $this->router = $router; $this->logger = $logger; $this->clientId = $clientId; $this->clientSecret = $clientSecret; }

Slide 26

Slide 26 text

function createToken(Request $request, $providerKey) { $request = $this->client->post(…); $response = $request->send(); $data = $response->json(); ! if (isset($data['error'])) { $message = ‘An error occured…’; $this->logger->notice($message); throw new HttpException(401, $message); } ! return new PreAuthenticatedToken( ‘anon.', $data[‘access_token'], $providerKey ); }

Slide 27

Slide 27 text

ZOOM IN

Slide 28

Slide 28 text

STEP 1: GET ACCESS TOKEN $url = $this->router->generate(‘admin’,[], true); $endpoint = ‘/login/oauth/access_token’; ! $request = $this->client->post($endpoint,[], [ 'client_id' => $this->clientId, 'client_secret' => $this->clientSecret, 'code' => $request->get('code'), 'redirect_uri' => $url ]); ! $response = $request->send(); $data = $response->json();

Slide 29

Slide 29 text

STEP 2: IF ERROR FROM GITHUB, EXCEPTION if (isset($data['error'])) { $message = 'An error occured during authentication with Github.'; $this->logger->notice($message, [ 'HTTP_CODE_STATUS' => 401, ‘error' => $data['error'], 'error_description' => $data['error_description'], ]); ! throw new HttpException(401, $message); }

Slide 30

Slide 30 text

STEP 3: CREATE TOKEN return new PreAuthenticatedToken( 'anon.', $data[‘access_token’], $providerKey );

Slide 31

Slide 31 text

LET’S TEST IT!

Slide 32

Slide 32 text

WHAT TO TEST?

Slide 33

Slide 33 text

public function createToken(Request $request, $providerKey) { $request = $this->client->post('/login/oauth/access_token', array(), array ( 'client_id' => $this->clientId, 'client_secret' => $this->clientSecret, 'code' => $request->get('code'), 'redirect_uri' => $this->router->generate('admin', array(), UrlGeneratorInterface::ABSOLUTE_URL) )); ! $response = $request->send(); ! $data = $response->json(); ! if (isset($data['error'])) { $message = sprintf('An error occured during authentication with Github. (%s)', $data['error_description']); $this->logger->notice( $message, array( 'HTTP_CODE_STATUS' => Response::HTTP_UNAUTHORIZED, 'error' => $data['error'], 'error_description' => $data['error_description'], ) ); ! throw new HttpException(Response::HTTP_UNAUTHORIZED, $message); } ! return new PreAuthenticatedToken( ‘anon.', $data[‘access_token'], $providerKey ); } We need to test the result of this method

Slide 34

Slide 34 text

CREATE THE TEST CLASS

Slide 35

Slide 35 text

namespace PoleDev\AppBundle\Tests\Security; ! class GithubAuthenticatorTest extends \PHPUnit_Framework_TestCase { public function testCreateToken() { } }

Slide 36

Slide 36 text

LET’S CALL THE CODE WE NEED TO TEST

Slide 37

Slide 37 text

LET’S GET OUR DUMMIES AND CALL OUR METHOD TO TEST public function testCreateToken() { $githubAuthenticator = new GithubAuthenticator( $client, $router, $logger, '', '' ); ! $token = $githubAuthenticator ->createToken($request, ‘secure_area') ; }

Slide 38

Slide 38 text

To construct the GithubAuthenticator, we need: • Guzzle\Service\Client • Symfony\Bundle\FrameworkBundle\Routing\Router • Psr\Log\LoggerInterface • $clientId = ‘’ • $clientSecret = ‘’

Slide 39

Slide 39 text

To call the createToken() method, we need ! • Symfony\Component\HttpFoundation\Request • $providerKey = 'secure_area'

Slide 40

Slide 40 text

$client = $this->getMock(‘Guzzle\Service \Client’); ! $router = $this->getMock('Symfony\Bundle \FrameworkBundle\Routing\Router'); ! $logger = $this->getMock('Psr\Log \LoggerInterface'); ! $request = $this->getMock('Symfony\Component \HttpFoundation\Request'); This a dummy

Slide 41

Slide 41 text

No content

Slide 42

Slide 42 text

We must disable the use of the original Router constructor as we don’t actually care.

Slide 43

Slide 43 text

$router = $this->getMockBuilder('Symfony \Bundle\FrameworkBundle\Routing\Router') ->disableOriginalConstructor() ->getMock() ;

Slide 44

Slide 44 text

MacBook-Pro-de-Sarah:~/Documents/talks/ symfonycon-madrid-2014/code-exemple$ phpunit - c app/ src/PoleDev/AppBundle/Tests/Security/ GithubAuthenticatorTest.php ! ! PHPUnit 4.3.5 by Sebastian Bergmann.! Configuration read from /Users/saro0h/ Documents/talks/symfonycon-madrid-2014/code- exemple/app/phpunit.xml.dist! ! PHP Fatal error: Call to a member function send() on null in /Users/saro0h/Documents/ talks/symfonycon-madrid-2014/code-exemple/src/ PoleDev/AppBundle/Security/ GithubAuthenticator.php on line 43

Slide 45

Slide 45 text

STEP 1: GET ACCESS TOKEN $url = $this->router->generate(‘admin’,[], true); $endpoint = ‘/login/oauth/access_token’; ! $request = $this->client->post($endpoint,[], [ 'client_id' => $this->clientId, 'client_secret' => $this->clientSecret, 'code' => $request->get('code'), 'redirect_uri' => $url ]); ! $response = $request->send(); $data = $response->json();

Slide 46

Slide 46 text

1/ We need to stub the call to the method $router->generate() it needs to return an url

Slide 47

Slide 47 text

! ! $router->method('generate')! ->with('admin',[], true)! ->willReturn(‘http://domain.name')! ;!

Slide 48

Slide 48 text

2/ We need to create a dummy Guzzle\Http\Message \EntityEnclosingRequest $guzzleRequest

Slide 49

Slide 49 text

$guzzleRequest = $this! ! ! ->getMockBuilder('Guzzle\Http \Message\EntityEnclosingRequest')! ->disableOriginalConstructor()! ->getMock()! ;

Slide 50

Slide 50 text

3/ We need to stub the call to the method $client- >post(), it needs to return a $guzzleRequest

Slide 51

Slide 51 text

$endpoint = '/login/oauth/access_token';! ! $client->method('post')! ->with($endpoint, [], [! 'client_id' => '',! 'client_secret' => '',! 'code' => '',! 'redirect_uri' => 'http://domain.name'! ])! ->willReturn($guzzleRequest)! ;

Slide 52

Slide 52 text

ORIGINAL CODE (STEP 1: GET ACCESS TOKEN) $url = $this->router->generate(‘admin’,[], true); $endpoint = ‘/login/oauth/access_token’; ! $request = $this->client->post($endpoint,[], [ 'client_id' => $this->clientId, 'client_secret' => $this->clientSecret, 'code' => $request->get('code'), 'redirect_uri' => $url ]); ! $response = $request->send(); $data = $response->json();

Slide 53

Slide 53 text

Create a stub for $request->send() This method must return a: Guzzle\Http\Message\Response $response ! Let’s go for it!

Slide 54

Slide 54 text

$guzzleResponse = $this->getMockBuilder('Guzzle\Http \Message\Response') ->disableOriginalConstructor() ->getMock() ; ! $guzzleRequest ->method(‘send') ->willReturn($guzzleResponse) ;

Slide 55

Slide 55 text

Hurray! The original code is running with our dummies and stubs. But we do not test anything.

Slide 56

Slide 56 text

A LITTLE REMINDER

Slide 57

Slide 57 text

public function createToken(Request $request, $providerKey) { $request = $this->client->post('/login/oauth/access_token', array(), array ( 'client_id' => $this->clientId, 'client_secret' => $this->clientSecret, 'code' => $request->get('code'), 'redirect_uri' => $this->router->generate('admin', array(), UrlGeneratorInterface::ABSOLUTE_URL) )); ! $response = $request->send(); ! $data = $response->json(); ! if (isset($data['error'])) { $message = sprintf('An error occured during authentication with Github. (%s)', $data['error_description']); $this->logger->notice( $message, array( 'HTTP_CODE_STATUS' => Response::HTTP_UNAUTHORIZED, 'error' => $data['error'], 'error_description' => $data['error_description'], ) ); ! throw new HttpException(Response::HTTP_UNAUTHORIZED, $message); } ! return new PreAuthenticatedToken( ‘anon.', $data[‘access_token'], $providerKey ); } We need to test the result of this method

Slide 58

Slide 58 text

TEST THAT THE TOKEN IS WHAT WE NEED TO BE $token = $githubAuthenticator->createToken($request, ‘secure_area’);! ! $this->assertSame('a_fake_access_token', $token->getCredentials());! $this->assertSame('secure_area', $token->getProviderKey());! $this->assertSame('anon.', $token->getUser());! $this->assertEmpty($token->getRoles());! $this->assertFalse($token->isAuthenticated());! $this->assertEmpty($token->getAttributes());! !

Slide 59

Slide 59 text

No content

Slide 60

Slide 60 text

Duck! Our token has its credentials at null. You need to provide it as the real code would have done it. ! Github returns an access token at that point.

Slide 61

Slide 61 text

$guzzleResponse! ->method('json')! ->willReturn([! ‘access_token' => ! ‘a_fake_access_token’! ])! ;

Slide 62

Slide 62 text

Hurray! The original code is running with our dummies and stubs. And it is tested !

Slide 63

Slide 63 text

USING A MOCK THIS TIME $guzzleResponse! ->expects($this->once())! ->method('json')! ->willReturn([! ‘access_token' => ‘a_fake_access_token'! ])! ; Expectation create a new assertion.

Slide 64

Slide 64 text

PROPHECY

Slide 65

Slide 65 text

INSTALL IT

Slide 66

Slide 66 text

FUNDAMENTALS

Slide 67

Slide 67 text

PROPHET

Slide 68

Slide 68 text

PROPHET $prophet = new \Prophecy\Prophet;

Slide 69

Slide 69 text

DUMMY

Slide 70

Slide 70 text

DUMMY $routerObjectProphecy = $prophet ->prophesize(‘Symfony\Bundle\FrameworkBundle\Routing\Router') ; ! $router = $routerObjectProphecy->reveal(); dummy

Slide 71

Slide 71 text

PROPHECY

Slide 72

Slide 72 text

PROPHECY • Object used to describe the future of your objects. ! $prophecy = $prophet->prophesize(‘YourClass’);

Slide 73

Slide 73 text

Note that the prophet won’t use the original constructor.

Slide 74

Slide 74 text

OBJECT DOUBLE The goal is to get the object double ! $prophecy->reveal();

Slide 75

Slide 75 text

STUB 1/2 • Get a stub out from the Router of Symfony. • $router->generate() must return http://www.google.com

Slide 76

Slide 76 text

STUB 2/2 $prophecy ->generate(‘route_name’) ->willReturn(‘http://www.google.com’) ;

Slide 77

Slide 77 text

PROMISE

Slide 78

Slide 78 text

• willReturn() => is actually a promise.

Slide 79

Slide 79 text

A promise is a piece of code allowing that a method call with a certain argument (if there is one), returns always the same value.

Slide 80

Slide 80 text

$prophecy->willReturn(‘my value’); Returns the value ‘my value’. $prophecy->willReturnArgument(); Returns the first method argument. $prophecy->willThrow(‘ExceptionClass’); Throws an exception. ! ! $prophecy->will($callback) ! $prophecy->will(new Prophecy\Promise \ReturnPromise(array(‘my value’)); === $prophecy->willReturn(‘my value’); PROMISE - API https://github.com/phpspec/prophecy/blob/master/src/Prophecy/Prophecy/MethodProphecy.php Details about the implementation:

Slide 81

Slide 81 text

NOT ENOUGH? Implement the Prophecy\Promise\PromiseInterface

Slide 82

Slide 82 text

ARGUMENT

Slide 83

Slide 83 text

HARD CODED ARGUMENT $prophecy ->generate(‘route_name’) ->willReturn(‘http://www.google.com’) ;

Slide 84

Slide 84 text

NO HARD CODE • Prophecy offers you plenty of methods to « wildcard » the arguments • Any argument is ok for the method you are « stubbing » Prophecy\Argument::any()

Slide 85

Slide 85 text

$prophecy ->myMethod(Prophecy\Argument::any()) ;

Slide 86

Slide 86 text

ARGUMENT THE API • Pretty complete • To go further with this https://github.com/phpspec/prophecy#arguments-wildcarding =>

Slide 87

Slide 87 text

MOCK

Slide 88

Slide 88 text

ADD EXPECTATIONS $prophecy ->generate(‘route_name’) ->willReturn(‘http://www.google.com’) ->shouldBeCalled() ;

Slide 89

Slide 89 text

We expect to have that method generate() called at least one time. How we call it in real life ? Predictions!

Slide 90

Slide 90 text

PREDICTIONS API ShouldBeCalled() shouldNotBeCalled() shouldBeCalledTimes(2)

Slide 91

Slide 91 text

NOT ENOUGH? Implement the Prediction\PredictionInterface

Slide 92

Slide 92 text

$prophet->checkPredictions(); ! (in your tearDown() method to check for all tests)

Slide 93

Slide 93 text

If the prediction fails, it throws an exception.

Slide 94

Slide 94 text

SPIES

Slide 95

Slide 95 text

Verifies that a method has been called during the execution ! $em = $prophet->prophesize('Doctrine\ORM\EntityManager'); ! $controller->createUser($em->reveal()); ! $em->flush()->shouldHaveBeenCalled(); Exemple taken from the official prophecy repository

Slide 96

Slide 96 text

REAL LIFE EXAMPLE

Slide 97

Slide 97 text

No content

Slide 98

Slide 98 text

No content

Slide 99

Slide 99 text

THE ORIGINAL CODE You don’t remember right…

Slide 100

Slide 100 text

namespace PoleDev\AppBundle\Security;! ! use Guzzle\Service\Client;! use Psr\Log\LoggerInterface;! use Symfony\[…]\Router;! use Symfony\[…]\Response;! use Symfony\[…]\Request;! use Symfony\[…]\SimplePreAuthenticatorInterface;! use Symfony\[…]\AuthenticationFailureHandlerInterface;! use Symfony\[…]\TokenInterface;! use Symfony\[…]\UserProviderInterface;! use Symfony\[…]\AuthenticationException;! use Symfony\[…]\UrlGeneratorInterface;! use Symfony\[…]\HttpException;! use Symfony\[…]\PreAuthenticatedToken;! ! class GithubAuthenticator implements SimplePreAuthenticatorInterface, AuthenticationFailureHandlerInterface! {! ! // Some code…! }

Slide 101

Slide 101 text

private $client; private $router; private $logger; ! public function __construct( Client $client, Router $router, LoggerInterface $logger, $clientId, $clientSecret ) { $this->client = $client; $this->router = $router; $this->logger = $logger; $this->clientId = $clientId; $this->clientSecret = $clientSecret; }

Slide 102

Slide 102 text

function createToken(Request $request, $providerKey) { $request = $this->client->post(…); $response = $request->send(); $data = $response->json(); ! if (isset($data['error'])) { $message = ‘An error occured…’; $this->logger->notice($message); throw new HttpException(401, $message); } ! return new PreAuthenticatedToken( ‘anon.', $data[‘access_token'], $providerKey ); }

Slide 103

Slide 103 text

FOCUS

Slide 104

Slide 104 text

STEP 1: GET ACCESS TOKEN $url = $this->router->generate(‘admin’,[], true); $endpoint = ‘/login/oauth/access_token’; ! $request = $this->client->post($endpoint,[], [ 'client_id' => $this->clientId, 'client_secret' => $this->clientSecret, 'code' => $request->get('code'), 'redirect_uri' => $url ]); ! $response = $request->send(); $data = $response->json();

Slide 105

Slide 105 text

STEP 2: IF ERROR FROM GITHUB, EXCEPTION if (isset($data['error'])) { $message = 'An error occured during authentication with Github.'; $this->logger->notice($message, [ 'HTTP_CODE_STATUS' => 401, ‘error' => $data['error'], 'error_description' => $data['error_description'], ]); ! throw new HttpException(401, $message); }

Slide 106

Slide 106 text

STEP 3: CREATE TOKEN return new PreAuthenticatedToken( 'anon.', $data[‘access_token’], $providerKey );

Slide 107

Slide 107 text

LET’S TEST IT! AGAIN !

Slide 108

Slide 108 text

WHAT TO TEST?

Slide 109

Slide 109 text

public function createToken(Request $request, $providerKey) { $request = $this->client->post('/login/oauth/access_token', array(), array ( 'client_id' => $this->clientId, 'client_secret' => $this->clientSecret, 'code' => $request->get('code'), 'redirect_uri' => $this->router->generate('admin', array(), UrlGeneratorInterface::ABSOLUTE_URL) )); ! $response = $request->send(); ! $data = $response->json(); ! if (isset($data['error'])) { $message = sprintf('An error occured during authentication with Github. (%s)', $data['error_description']); $this->logger->notice( $message, array( 'HTTP_CODE_STATUS' => Response::HTTP_UNAUTHORIZED, 'error' => $data['error'], 'error_description' => $data['error_description'], ) ); ! throw new HttpException(Response::HTTP_UNAUTHORIZED, $message); } ! return new PreAuthenticatedToken( ‘anon.', $data[‘access_token'], $providerKey ); } We need to test the result of this method

Slide 110

Slide 110 text

CREATE THE TEST CLASS

Slide 111

Slide 111 text

namespace PoleDev\AppBundle\Tests\Security; ! class GithubAuthenticatorTest extends \PHPUnit_Framework_TestCase { public function testCreateToken() { } }

Slide 112

Slide 112 text

FIRST, GET THE PROPHET namespace PoleDev\AppBundle\Tests\Security; ! class GithubAuthenticatorTest extends \PHPUnit_Framework_TestCase { private $prophet; ! public function testCreateToken() { } ! public function setUp() { $this->prophet = new \Prophecy\Prophet; } ! public function tearDown() { $this->prophet = null; } }

Slide 113

Slide 113 text

LET’S GET OUR DUMMIES AND CALL OUR METHOD TO TEST public function testCreateToken() { $githubAuthenticator = new GithubAuthenticator( $client, $router, $logger, '', '' ); ! $token = $githubAuthenticator ->createToken($request, ‘secure_area') ; }

Slide 114

Slide 114 text

To construct the GithubAuthenticator, we need • Guzzle\Service\Client • Symfony\Bundle\FrameworkBundle\Routing\Router • Psr\Log\LoggerInterface • $clientId = ‘’ • $clientSecret = ‘’

Slide 115

Slide 115 text

To call the createToken() method, we need ! • Symfony\Component\HttpFoundation\Request • $providerKey = 'secure_area'

Slide 116

Slide 116 text

LET’S GET OUR DUMMIES AND CALL OUR METHOD TO TEST public function testCreateToken() { $clientObjectProphecy = $this->prophet->prophesize('Guzzle\Service\Client'); $client = $clientObjectProphecy->reveal(); ! // … } This a prophecy This a dummy

Slide 117

Slide 117 text

$routerObjectProphecy = $this->prophet ->prophesize('Symfony\Bundle\FrameworkBundle \Routing\Router'); $router = $routerObjectProphecy->reveal(); ! $loggerObjectProphecy = $this->prophet ->prophesize('Psr\Log\LoggerInterface'); $logger = $loggerObjectProphecy->reveal(); ! $requestObjectProphecy = $this->prophet ->prophesize('Symfony\Component\HttpFoundation \Request'); $request = $requestObjectProphecy->reveal();

Slide 118

Slide 118 text

MacBook-Pro-de-Sarah:~/Documents/talks/ symfonycon-madrid-2014/code-exemple$ phpunit - c app/ src/PoleDev/AppBundle/Tests/Security/ GithubAuthenticatorTest.php ! ! PHPUnit 4.3.5 by Sebastian Bergmann.! Configuration read from /Users/saro0h/ Documents/talks/symfonycon-madrid-2014/code- exemple/app/phpunit.xml.dist! ! PHP Fatal error: Call to a member function send() on null in /Users/saro0h/Documents/ talks/symfonycon-madrid-2014/code-exemple/src/ PoleDev/AppBundle/Security/ GithubAuthenticator.php on line 43

Slide 119

Slide 119 text

The null object expected is a: Guzzle\Http\Message\EntityEnclosingRequest $request ! ! Let’s provide it!

Slide 120

Slide 120 text

$guzzleRequestObjectProphecy = $this ->prophet ->prophesize(‘Guzzle\Http\Message\EntityEnclosingRequest') ; ! $guzzleRequest = $guzzleRequestObjectProphecy->reveal();

Slide 121

Slide 121 text

ORIGINAL CODE (STEP 1: GET ACCESS TOKEN) $url = $this->router->generate(‘admin’,[], true); $endpoint = ‘/login/oauth/access_token’; ! $request = $this->client->post($endpoint,[], [ 'client_id' => $this->clientId, 'client_secret' => $this->clientSecret, 'code' => $request->get('code'), 'redirect_uri' => $url ]); ! $response = $request->send(); $data = $response->json();

Slide 122

Slide 122 text

$clientObjectProphecy = $this->prophet! ->prophesize(‘Guzzle\Service\Client');! ! $clientObjectProphecy! ->post('/login/oauth/access_token', [], [! 'client_id' => ' ',! 'client_secret' => ' ',! 'code' => ' ',! 'redirect_uri' => ' '! ])! ->willReturn($guzzleRequest)! ;! ! $client = $clientObjectProphecy->reveal();

Slide 123

Slide 123 text

ORIGINAL CODE (STEP 1: GET ACCESS TOKEN) $url = $this->router->generate(‘admin’,[], true); $endpoint = ‘/login/oauth/access_token’; ! $request = $this->client->post($endpoint,[], [ 'client_id' => $this->clientId, 'client_secret' => $this->clientSecret, 'code' => $request->get('code'), 'redirect_uri' => $url ]); ! $response = $request->send(); $data = $response->json();

Slide 124

Slide 124 text

Create a stub for $request->send() This method must return a: Guzzle\Http\Message\Response $response ! Let’s go for it!

Slide 125

Slide 125 text

$guzzleResponseObjectProphecy = $this->prophet ->prophesize('Guzzle\Http\Message\Response'); $guzzleResponse = $guzzleResponseObjectProphecy->reveal(); ! $guzzleRequestObjectProphecy = $this ->prophet ->prophesize(‘Guzzle\Http\Message\EntityEnclosingRequest') ; ! $guzzleRequestObjectProphecy ->send() ->willReturn($guzzleResponse) ; ! $guzzleRequest = $guzzleRequestObjectProphecy->reveal();

Slide 126

Slide 126 text

Hurray! The original code is running with our dummies and stubs. But we do not test anything.

Slide 127

Slide 127 text

WHAT WE NEED TO TEST AGAIN?

Slide 128

Slide 128 text

public function createToken(Request $request, $providerKey) { $request = $this->client->post('/login/oauth/access_token', array(), array ( 'client_id' => $this->clientId, 'client_secret' => $this->clientSecret, 'code' => $request->get('code'), 'redirect_uri' => $this->router->generate('admin', array(), UrlGeneratorInterface::ABSOLUTE_URL) )); ! $response = $request->send(); ! $data = $response->json(); ! if (isset($data['error'])) { $message = sprintf('An error occured during authentication with Github. (%s)', $data['error_description']); $this->logger->notice( $message, array( 'HTTP_CODE_STATUS' => Response::HTTP_UNAUTHORIZED, 'error' => $data['error'], 'error_description' => $data['error_description'], ) ); ! throw new HttpException(Response::HTTP_UNAUTHORIZED, $message); } ! return new PreAuthenticatedToken( ‘anon.', $data[‘access_token'], $providerKey ); } We need to test the result of this method

Slide 129

Slide 129 text

TEST THAT THE TOKEN IS WHAT WE NEED TO BE $token = $githubAuthenticator->createToken($request, ‘secure_area’);! ! $this->assertSame('a_fake_access_token', $token->getCredentials());! $this->assertSame('secure_area', $token->getProviderKey());! $this->assertSame('anon.', $token->getUser());! $this->assertEmpty($token->getRoles());! $this->assertFalse($token->isAuthenticated());! $this->assertEmpty($token->getAttributes());! !

Slide 130

Slide 130 text

No content

Slide 131

Slide 131 text

Remember that output… ! Github returns an access token at that point.

Slide 132

Slide 132 text

$guzzleResponseObjectProphecy = $this->prophet- >prophesize('Guzzle\Http\Message\Response'); ! $guzzleResponseObjectProphecy ->json() ->willReturn([‘access_token' => ‘a_fake_access_token']) ; ! $guzzleResponse = $guzzleResponseObjectProphecy->reveal();

Slide 133

Slide 133 text

Hurray! The original code is running with our dummies and stubs. And it is tested !

Slide 134

Slide 134 text

USING A MOCK THIS TIME ! $guzzleResponseObjectProphecy ->json() ->willReturn(array(‘access_token' => 'a_fake_access_token')) ->shouldBeCalledTimes(1) ; Don’t expect to get a new assertion, as in PHPUnit

Slide 135

Slide 135 text

public function tearDown()! {! $this->prophet->checkPredictions();! $this->prophet = null;! } Mandatory DON’T FORGET ABOUT THE CHECK

Slide 136

Slide 136 text

WRONG EXPECTATION But if the expectation is not right, you’ll get an exception. ! $guzzleResponseObjectProphecy ->json() ->willReturn(array(‘access_token' => 'a_fake_access_token')) ->shouldBeCalledTimes(10) ;

Slide 137

Slide 137 text

No content

Slide 138

Slide 138 text

TO SUM UP ALL OF THIS

Slide 139

Slide 139 text

The Prophecy mock library philosophy is around the description of the future of an object double through a prophecy. Prophecy

Slide 140

Slide 140 text

A prophecy must be revealed to get a dummy, a stub or a mock. Prophecy

Slide 141

Slide 141 text

With PHPUnit, all is around the getMock() method. PHPUnit

Slide 142

Slide 142 text

The first step is to get a mock, then describe the future of the double of the object. PHPUnit PHPUnit

Slide 143

Slide 143 text

$dummy = $this->getMock(‘Foo\Bar’); ! ——————————————————————————————————————— ! $prophet = new \Prophecy\Prophet; $prophecy = $prophet->prophesize(‘Foo\Bar’); $dummy = $prophecy->reveal(); PHPUnit Prophecy

Slide 144

Slide 144 text

$prophecy ->send() ->willReturn($valueToReturn) ; —————————————————————————————————————————— $dummy ->method('send') ->willReturn($valueToReturn) ; Prophecy PHPUnit

Slide 145

Slide 145 text

Extensible. ! ——————————————————————————————————————— ! Not extensible. Prophecy PHPUnit

Slide 146

Slide 146 text

AWSOMNESS!

Slide 147

Slide 147 text

No content

Slide 148

Slide 148 text

Resources • https://github.com/phpspec/prophecy • https://phpunit.de/manual/4.5/en/test- doubles.html#test-doubles.prophecy • all the code is here: http://bit.ly/11pnp2I

Slide 149

Slide 149 text

Thank you! @saro0h joind.in/talk/view/12957 sarah-khalil.com/talks saro0h