28
ZürichPHP UG 18.06.2015 Pascal Thormeier

PHP-VCR behat case study

Embed Size (px)

Citation preview

ZürichPHP UG 18.06.2015Pascal Thormeier

Setup

Big Symfony2 project for large Swiss company

Multiple attached APIs

Tests in both PHPUnit and Behat/PhantomJS

Gitlab CI and vagrant for development

ONCE UPON A TIME

History

Testing with manually created fixtures

Mocks had to be adjusted when APIs changed

Using Guzzle's mock plugin

Symfony's profiler helped out quite a lot...

Mocking:

• Do API call you would like to mock

• Copy/paste response to JSON/XML file

• Adjust to test cases

• Re-do whole fixture when small things changed

History

Behat tests couldn't really be mocked

Built an application that proxies API calls and runs in the back in an own container

Loads of problems with that solution

History

Switching to PHP-VCR

Behat testing and mocking became very well possible

Old way of creating fixtures was no longer necessary

History

WHAT ARE WE DOING NOW?

Behat

Testing framework

Works with different so called drivers

Tests are written in Gherkin

We're using the Selenium2 and Symfony Web drivers

Selenium

PhantomJS as headless browser

PHP -> node.js -> PHP

... which leads to a problem

Selenium

Two different, independent PHP processes

Which fixture should the application load?

Selenium

Let's just use a cookie.

Selenium

<?phpclass AcmeContext extends MinkContext { const COOKIE_NAME = 'BehatCassetteName';!// ...! public function before(BeforeScenarioScope $scope) { $this->getSession()->setCookie( self::COOKIE_NAME, $this->getCassetteName($scope) ); }!// ...!}

Selenium<?phpclass AcmeContext extends MinkContext {!// ...! public function getCasetteName($scope) { return 'fixtures/' . self::normalize($scope->getFeature()->getTitle()) . '/' . self::normalize($scope->getScenario()->getTitle()) ; }! public static function normalize($string) { return trim(preg_replace( '/[^a-zA-Z0-9]+/', '_', $string ), '_'); }!// ...!}

GherkinFeature: Some terse yet descriptive text of what is desired In order to realize a named business value As an explicit system actor I want to gain some beneficial outcome which furthers the goal! Additional text...! Scenario: Some determinable business situation Given some precondition And some other precondition When some action by the actor And some other action And yet another action Then some testable outcome is achieved And something else we can check happens too! Scenario: A different situation ...

Selenium<?php// web/app_test.php - Remove this file while deployment!!// ...!// Execute standard SF2 stuff$kernel = new AppKernel('test', true);$kernel->loadClassCache();Request::enableHttpMethodParameterOverride();$request = Request::createFromGlobals();!// Set up VCR with fixture name from cookie$fixture = $request->cookies->get(AcmeContext::COOKIE_NAME, null);if (!$fixture) { $fixture = AcmeContext::normalize($request->getUri());}VCR::turnOn(); VCR::insertCassette('../tests/' . $fixture . '.json');!// Execute standard SF2 stuff$response = $kernel->handle($request);$response->send();$kernel->terminate($request, $response);!// Save the fixtureVCR::eject();VCR::turnOff();

Selenium

Neat side effects:

• Developing without internet!

• Change API responses for development

Minor pitfall:

• PhantomJS doesn't send this cookie when doing AJAX

• Fixtures for AJAX would be named after the URL, which works but isn't as nice

• You have to set the cookie manually in JS again

Selenium

WebDriver

We want this in the SF WebDriver as well

Move it from app_test to AppKernel

Keep app_test.php as entry point for PhantomJS

WebDriver<?phpclass AppKernel extends Kernel{ // ... public function handle(Request $request, ...) { if ('test' === $this->getEnvironment()) { $fixture = $request->cookies->get( AcmeContext::COOKIE_NAME, null );! if (!$fixture) { $fixture = AcmeContext::normalize( $request->getUri() ); }! VCR::turnOn(); VCR::insertCassette('../tests/' . $fixture . '.json'); }! return parent::handle($request, ...); } // ...}

WebDriver

Would only need minor adjustments to also work in PHPUnit/Symfony WebTestCase

GitlabCI

CI is executing tests as well

Tests might not be mocked

Test will be mocked

Conflicts while making a new build

GitlabCI

<?php// ...!if ($container->hasParameter('is_ci')) { VCR::configure()->setMode(VCR::MODE_NONE);}!// ...

WRAP UP

Wrap up

PHP-VCR is awesome

But switching all fixtures to PHP-VCR might be a lot of work

But you don't need to write them yourselves anymore

Wrap up

Some pitfalls:

• Tests without fixtures

• Setup depending on framework might be difficult

• Not very transparent where data comes from inside tests

• First test run to write all the fixtures is slow

Wrap up

Would highly recommend it because it saves you a lot of time, work and nerves

Info

Info and documentation: http://php-vcr.github.io

Source: https://github.com/php-vcr/php-vcr

VCR (ruby): https://github.com/vcr/vcr

Follow: @pthormeier and @adrian_philipp