Slide 1

Slide 1 text

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

Slide 2

Slide 2 text

WHO AM I? • Head of • Trainer & Developer • Enjoying sharer • Contributor to

Slide 3

Slide 3 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 4

Slide 4 text

TERMINOLOGY

Slide 5

Slide 5 text

Dummies are objects that are passed around but never used. Fulfills a contract.

Slide 6

Slide 6 text

Stubs are objects that implement the same methods as the real objects. Returns a specific value.

Slide 7

Slide 7 text

Mocks are pre-programmed with expectations which forming 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. Has expectation.

Slide 8

Slide 8 text

PHPUNIT MOCKING LIBRARY

Slide 9

Slide 9 text

INSTALL IT

Slide 10

Slide 10 text

FUNDAMENTALS

Slide 11

Slide 11 text

Your test class must extend PHPUnit_Framework_TestCase

Slide 12

Slide 12 text

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

Slide 13

Slide 13 text

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

Slide 14

Slide 14 text

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

Slide 15

Slide 15 text

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

Slide 16

Slide 16 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 17

Slide 17 text

REAL LIFE EXAMPLE

Slide 18

Slide 18 text

No content

Slide 19

Slide 19 text

No content

Slide 20

Slide 20 text

Who knows exactly how the Oauth dance of Github works?

Slide 21

Slide 21 text

IMPLEMENTATION OF THE FEATURE

Slide 22

Slide 22 text

namespace PoleDev\AppBundle\Security; use Guzzle\Service\Client; 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 23

Slide 23 text

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

Slide 24

Slide 24 text

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

Slide 25

Slide 25 text

ZOOM IN

Slide 26

Slide 26 text

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

Slide 27

Slide 27 text

PART 2/3: 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); } createToken()

Slide 28

Slide 28 text

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

Slide 29

Slide 29 text

No content

Slide 30

Slide 30 text

No content

Slide 31

Slide 31 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 createToken method

Slide 32

Slide 32 text

namespace PoleDev\AppBundle\Tests\Security; class GithubAuthenticatorTest extends \PHPUnit_Framework_TestCase { public function testCreateToken() { } } CREATE THE TEST CLASS

Slide 33

Slide 33 text

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

Slide 34

Slide 34 text

public function testCreateToken() { $githubAuthenticator = new GithubAuthenticator( $client, $router, '', '' ); $token = $githubAuthenticator ->createToken($request, ‘secure_area') ; } Where do they come from?

Slide 35

Slide 35 text

To construct the GithubAuthenticator, we need: • Guzzle\Service\Client • Symfony\Bundle\FrameworkBundle\Routing\Router • $clientId = ‘’ • $clientSecret = ‘’ Step 1 fulfill the contract

Slide 36

Slide 36 text

To call the createToken() method, we need: • Symfony\Component\HttpFoundation\Request • $providerKey = 'secure_area' Step 1 fulfill the contract

Slide 37

Slide 37 text

$client = $this->getMock(‘Guzzle\Service \Client’); $router = $this->getMock('Symfony\Bundle \FrameworkBundle\Routing\Router'); $request = $this->getMock('Symfony\Component \HttpFoundation\Request'); This a dummy Step 1 fulfill the contract

Slide 38

Slide 38 text

RUN THE TEST SUITE

Slide 39

Slide 39 text

Is it really all about contracts? Step 1 fulfill the contract

Slide 40

Slide 40 text

Step 1 fulfill the contract

Slide 41

Slide 41 text

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

Slide 42

Slide 42 text

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

Slide 43

Slide 43 text

$ phpunit -c app/ src/PoleDev/AppBundle/Tests/ Security/GithubAuthenticatorTest.php PHP Fatal error: Call to a member function send() on null in /[…]/ AppBundle/Security/ GithubAuthenticator.php on line 43 Step 2 get the router

Slide 44

Slide 44 text

4 OBJECTS TO FAKE public function createToken(Request $request, $providerKey) { // … $url = $this->router->generate(‘admin’,[], true); $endpoint = ‘/login/oauth/access_token’; $guzzleRequest = $this->client->post($endpoint,[], [ 'client_id' => $this->clientId, 'client_secret' => $this->clientSecret, 'code' => $request->get('code'), 'redirect_uri' => $url ]); $response = $guzzleRequest->send(); $data = $response->json(); } 1 3 2 4 Step 3 fake data from Github

Slide 45

Slide 45 text

public function createToken(Request $request, $providerKey) { // … $url = $this->router->generate(‘admin’,[], true); $endpoint = ‘/login/oauth/access_token’; $guzzleRequest = $this->client->post($endpoint,[], [ 'client_id' => $this->clientId, 'client_secret' => $this->clientSecret, 'code' => $request->get('code'), 'redirect_uri' => $url ]); $response = $guzzleRequest->send(); $data = $response->json(); } 1 Step 3 fake data from Github

Slide 46

Slide 46 text

We need to stub the call to the method $router->generate() it needs to return an url 1 Step 3 fake data from Github

Slide 47

Slide 47 text

$router ->method('generate') ->with('admin',[], true) ->willReturn('http://domain.name') ; 1 STUB THE ROUTER::GENERATE Step 3 fake data from Github

Slide 48

Slide 48 text

public function createToken(Request $request, $providerKey) { // … $url = $this->router->generate(‘admin’,[], true); $endpoint = ‘/login/oauth/access_token’; $guzzleRequest = $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(); } 2 Step 3 fake data from Github

Slide 49

Slide 49 text

We need to create a dummy Guzzle\Http\Message \EntityEnclosingRequest $guzzleRequest 2 Step 3 fake data from Github

Slide 50

Slide 50 text

$guzzleRequest = $this ->getMockBuilder('Guzzle\Http \Message\EntityEnclosingRequest') ->disableOriginalConstructor() ->getMock() ; 2 GET THE GUZZLE REQUEST DUMMY Step 3 fake data from Github

Slide 51

Slide 51 text

public function createToken(Request $request, $providerKey) { // … $url = $this->router->generate(‘admin’,[], true); $endpoint = ‘/login/oauth/access_token’; $guzzleRequest = $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(); } 3 Step 3 fake data from Github

Slide 52

Slide 52 text

We need to stub the call to the method $client->post(), it needs to return a $guzzleRequest 3 Step 3 fake data from Github

Slide 53

Slide 53 text

$endpoint = '/login/oauth/access_token'; $client ->method('post') ->with($endpoint, [], [ 'client_id' => '', 'client_secret' => '', 'code' => '', 'redirect_uri' => 'http://domain.name' ]) ->willReturn($guzzleRequest) ; 3 STUB THE GUZZLECLIENT::POST Step 3 fake data from Github

Slide 54

Slide 54 text

public function createToken(Request $request, $providerKey) { // … $url = $this->router->generate(‘admin’,[], true); $endpoint = ‘/login/oauth/access_token’; $guzzleRequest = $this->client->post($endpoint,[], [ 'client_id' => $this->clientId, 'client_secret' => $this->clientSecret, 'code' => $request->get('code'), 'redirect_uri' => $url ]); $response = $guzzleRequest->send(); $data = $response->json(); } 4 Step 3 fake data from Github

Slide 55

Slide 55 text

This method must return a: Guzzle\Http\Message\Response $response Let’s go for it! Step 3 fake data from Github STUB THE GUZZLEREQUEST::SEND 4

Slide 56

Slide 56 text

$guzzleResponse = $this->getMockBuilder('Guzzle \Http\Message\Response') ->disableOriginalConstructor() ->getMock() ; $guzzleRequest ->method(‘send') ->willReturn($guzzleResponse) ; 4 Step 3 fake data from Github STUB THE GUZZLEREQUEST::SEND

Slide 57

Slide 57 text

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

Slide 58

Slide 58 text

No content

Slide 59

Slide 59 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 createToken method

Slide 60

Slide 60 text

$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()); Test that the TOKEN is what we need to be. Step 4 assertions

Slide 61

Slide 61 text

Step 3 bis forgot a thing (sorry)

Slide 62

Slide 62 text

No content

Slide 63

Slide 63 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 64

Slide 64 text

$guzzleResponse ->method('json') ->willReturn([ ‘access_token' => ‘a_fake_access_token’ ]) ; Step 3 bis forgot a thing (sorry)

Slide 65

Slide 65 text

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

Slide 66

Slide 66 text

No content

Slide 67

Slide 67 text

ADD EXPECTATION: MOCK $guzzleResponse ->expects($this->once()) ->method('json') ->willReturn([ ‘access_token' => ‘a_fake_access_token' ]) ; Expectation creates a new assertion.

Slide 68

Slide 68 text

PROPHECY

Slide 69

Slide 69 text

No content

Slide 70

Slide 70 text

INSTALL IT ;)

Slide 71

Slide 71 text

FUNDAMENTALS

Slide 72

Slide 72 text

PROPHET $prophet = new \Prophecy\Prophet;

Slide 73

Slide 73 text

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

Slide 74

Slide 74 text

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

Slide 75

Slide 75 text

Note that the prophet won’t use the original constructor by default. beConstructedWith($object) beConstructedThrough(‘method’, [$argument]) in PhpSpec

Slide 76

Slide 76 text

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

Slide 77

Slide 77 text

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

Slide 78

Slide 78 text

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

Slide 79

Slide 79 text

PROMISE

Slide 80

Slide 80 text

willReturn() is a promise.

Slide 81

Slide 81 text

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

Slide 82

Slide 82 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 83

Slide 83 text

NOT ENOUGH? Implement the Prophecy\Promise\PromiseInterface

Slide 84

Slide 84 text

ARGUMENT

Slide 85

Slide 85 text

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

Slide 86

Slide 86 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 87

Slide 87 text

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

Slide 88

Slide 88 text

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

Slide 89

Slide 89 text

MOCK

Slide 90

Slide 90 text

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

Slide 91

Slide 91 text

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

Slide 92

Slide 92 text

PREDICTIONS API shouldBeCalled() shouldNotBeCalled() shouldBeCalledTimes(2)

Slide 93

Slide 93 text

NOT ENOUGH? Implement the Prediction\PredictionInterface

Slide 94

Slide 94 text

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

Slide 95

Slide 95 text

If the prediction fails, it throws an exception.

Slide 96

Slide 96 text

SPIES

Slide 97

Slide 97 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 98

Slide 98 text

REAL LIFE EXAMPLE

Slide 99

Slide 99 text

No content

Slide 100

Slide 100 text

No content

Slide 101

Slide 101 text

THE ORIGINAL CODE You don’t remember right…

Slide 102

Slide 102 text

namespace PoleDev\AppBundle\Security; use Guzzle\Service\Client; 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 103

Slide 103 text

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

Slide 104

Slide 104 text

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

Slide 105

Slide 105 text

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

Slide 106

Slide 106 text

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

Slide 107

Slide 107 text

LET’S TEST IT! AGAIN !

Slide 108

Slide 108 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 createToken method

Slide 109

Slide 109 text

namespace PoleDev\AppBundle\Tests\Security; class GithubAuthenticatorTest extends \PHPUnit_Framework_TestCase { public function testCreateToken() { } } CREATE THE TEST CLASS

Slide 110

Slide 110 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 111

Slide 111 text

public function testCreateToken() { $githubAuthenticator = new GithubAuthenticator( $client, $router, '', '' ); $token = $githubAuthenticator ->createToken($request, ‘secure_area') ; } We need to create those dependencies.

Slide 112

Slide 112 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 113

Slide 113 text

$routerObjectProphecy = $this->prophet ->prophesize('Symfony\Bundle \FrameworkBundle\Routing\Router'); $router = $routerObjectProphecy->reveal(); $requestObjProphecy = $this->prophet ->prophesize('Symfony\Component \HttpFoundation\Request'); $request = $requestObjProphecy->reveal(); Step 1 fulfill the contract

Slide 114

Slide 114 text

$ phpunit -c app/ src/PoleDev/AppBundle/Tests/ Security/GithubAuthenticatorTest.php PHP Fatal error: Call to a member function send() on null in /[…]/ AppBundle/Security/ GithubAuthenticator.php on line 43 Step 2 get the router

Slide 115

Slide 115 text

3 OBJECTS TO FAKE public function createToken(Request $request, $providerKey) { // … $url = $this->router->generate(‘admin’,[], true); $endpoint = ‘/login/oauth/access_token’; $guzzleRequest = $this->client->post($endpoint,[], [ 'client_id' => $this->clientId, 'client_secret' => $this->clientSecret, 'code' => $request->get('code'), 'redirect_uri' => $url ]); $response = $guzzleRequest->send(); $data = $response->json(); } 2 1 3 Step 3 fake data from Github

Slide 116

Slide 116 text

public function createToken(Request $request, $providerKey) { // … $url = $this->router->generate(‘admin’,[], true); $endpoint = ‘/login/oauth/access_token’; $guzzleRequest = $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(); } 1 Step 3 fake data from Github

Slide 117

Slide 117 text

We need to create a dummy Guzzle\Http\Message \EntityEnclosingRequest $guzzleRequest 1 Step 3 fake data from Github

Slide 118

Slide 118 text

$guzzleRequestObjectProphecy = $this ->prophet ->prophesize(‘Guzzle\Http\Message\EntityEnclosingRequest') ; $guzzleRequest = $guzzleRequestObjectProphecy->reveal(); GET THE GUZZLE REQUEST DUMMY 1 Step 3 fake data from Github

Slide 119

Slide 119 text

public function createToken(Request $request, $providerKey) { // … $url = $this->router->generate(‘admin’,[], true); $endpoint = ‘/login/oauth/access_token’; $guzzleRequest = $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(); } 2 Step 3 fake data from Github

Slide 120

Slide 120 text

We need to stub the call to the method $client->post(), it needs to return a $guzzleRequest 2 Step 3 fake data from Github

Slide 121

Slide 121 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(); STUB THE GUZZLECLIENT::POST 2

Slide 122

Slide 122 text

public function createToken(Request $request, $providerKey) { // … $url = $this->router->generate(‘admin’,[], true); $endpoint = ‘/login/oauth/access_token’; $guzzleRequest = $this->client->post($endpoint,[], [ 'client_id' => $this->clientId, 'client_secret' => $this->clientSecret, 'code' => $request->get('code'), 'redirect_uri' => $url ]); $response = $guzzleRequest->send(); $data = $response->json(); } 3 Step 3 fake data from Github

Slide 123

Slide 123 text

This method must return a: Guzzle\Http\Message\Response $response Let’s go for it! 3 Step 3 fake data from Github STUB THE GUZZLECLIENT::SEND

Slide 124

Slide 124 text

$guzzleResponseObjectProphecy = $this->prophet ->prophesize('Guzzle\Http\Message\Response'); $guzzleResponse = $guzzleResponseObjectProphecy->reveal(); $guzzleRequestObjectProphecy ->send() ->willReturn($guzzleResponse) ; STUB THE GUZZLECLIENT::SEND 3

Slide 125

Slide 125 text

No content

Slide 126

Slide 126 text

$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()); Test that the TOKEN is what we need to be. Step 4 assertions

Slide 127

Slide 127 text

No content

Slide 128

Slide 128 text

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

Slide 129

Slide 129 text

$guzzleResponseObjectProphecy = $this->prophet- >prophesize('Guzzle\Http\Message\Response'); $guzzleResponseObjectProphecy ->json() ->willReturn([‘access_token' => ‘a_fake_access_token']) ; $guzzleResponse = $guzzleResponseObjectProphecy->reveal(); Step 3 bis forgot thing (again?!)

Slide 130

Slide 130 text

No content

Slide 131

Slide 131 text

$guzzleResponseObjectProphecy ->json() ->willReturn(array( ‘access_token' => ‘a_fake_access_token’ )) ->shouldBeCalledTimes(1) ; ADD EXPECTATION: MOCK Don’t expect to get a new assertion, as in PHPUnit

Slide 132

Slide 132 text

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

Slide 133

Slide 133 text

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

Slide 134

Slide 134 text

No content

Slide 135

Slide 135 text

No content

Slide 136

Slide 136 text

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

Slide 137

Slide 137 text

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

Slide 138

Slide 138 text

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

Slide 139

Slide 139 text

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

Slide 140

Slide 140 text

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

Slide 141

Slide 141 text

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

Slide 142

Slide 142 text

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

Slide 143

Slide 143 text

AWSOMNESS!

Slide 144

Slide 144 text

No content

Slide 145

Slide 145 text

No content

Slide 146

Slide 146 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 147

Slide 147 text

Thank you! @saro0h speakerdeck.com/saro0h/ saro0h This is a zero guys!