Testing Part 2: Faking the Event Lookup

Keep on Learning!

If you liked what you've learned so far, dive in!
Subscribe to get access to this tutorial plus
video, code and script downloads.

Start your All-Access Pass
Buy just this tutorial for $12.00

Let's run the test. Copy its method name, then open your terminal. It looks like PHPUnit installed just fine. So, run:

./vendor/bin/phpunit --filter testStripeCustomerSubscriptionDeleted

Oh no! It blew up! Hmmm:

Unknown database 'stripe_recording_test'

Ah, my bad!

I setup our project to use a different database for testing... and I forgot to create it! Do that with:

./bin/console doctrine:database:create --env=test

And to create the tables, run:

./bin/console doctrine:schema:create --env=test

Try the test again:

./vendor/bin/phpunit --filter testStripeCustomerSubscriptionDeleted

Another error! Scroll to the top! The webhook returned a 500 error. And if you look closely at the dumped response HTML, you can see the reason:

No such event: evt_00000000000000

Ah, the id of the fake event that we're sending is evt_00000000000000. That's not a real event in Stripe, and so when the WebhookController reads this and uses Stripe's API to fetch this event, it's not there:

... lines 1 - 8
class WebhookController extends BaseController
... lines 11 - 13
public function stripeWebhookAction(Request $request)
... lines 16 - 22
$stripeEvent = $this->get('stripe_client')
... lines 25 - 39
... lines 41 - 60

It's kind of funny: we added this API lookup to prevent a third-party from sending fake events... and now it's stopping us from doing exactly that. Dang!

Faking things in the Test Environment

Hmm, how to fix this? In the real world, we do want to use stripe's API to fetch the Event object. But in the test environment, this would all work if our code would simply use the JSON we're sending it as the event, and skip the lookup.

Let's do it! We'll set a special configuration variable in the test environment only, then use that to change our logic in the controller.

Open app/config/config.yml and add a new parameter: verify_stripe_event set to true:

... lines 1 - 7
... line 9
verify_stripe_event: true
... lines 11 - 80

Copy that, and open config_test.yml. Add a parameters key, paste this parameter, but override it to be false:

... lines 1 - 3
verify_stripe_event: false
... lines 6 - 25

Now, in WebhookController, we just need an if statement: if $this->getParameter('verify_stripe_event') is true, then keep the normal behavior. Otherwise, set $stripeEvent to json_decode($request->getContent()):

... lines 1 - 8
class WebhookController extends BaseController
... lines 11 - 13
public function stripeWebhookAction(Request $request)
... lines 16 - 22
if ($this->getParameter('verify_stripe_event')) {
$stripeEvent = $this->get('stripe_client')
} else {
// fake the Stripe_Event in the test environment
$stripeEvent = json_decode($request->getContent());
... lines 30 - 44
... lines 46 - 65

OK, this is not technically perfect: the first $stripeEvent is a \Stripe\Event object, and the second will be an instance of stdClass. But, since you fetch data off both the same way, it should work.

Let's see if does! Try the test again:

./vendor/bin/phpunit --filter testStripeCustomerSubscriptionDeleted

This time, no errors! And the dumped response content looks perfect: event handled.

Refreshing the Data after the Test

But, the test didn't pass:

Failed asserting that true is false on line 43

It looks like our webhook is not working, because the subscription is still active. But actually, that's not true: Doctrine is tricking us! In reality, the database has been updated to show that the Subscription is canceled, but this Subscription object is out-of-date. Query for a fresh one with $subscription = $this->em - I set the EntityManager on that property in setup() - then ->getRepository('AppBundle:Subscription') with find($subscription->getId()):

... lines 1 - 9
class WebhookControllerTest extends WebTestCase
... lines 12 - 22
public function testStripeCustomerSubscriptionDeleted()
... lines 25 - 31
$this->assertEquals(200, $client->getResponse()->getStatusCode());
$subscription = $this->em
... lines 48 - 126

This subscription will have fresh data.

Try the test!

./vendor/bin/phpunit --filter testStripeCustomerSubscriptionDeleted

And we are green!

I know, that was kind of hard! But if you want to have automated webhook tests, this is the way to do it. To make matters worse, for other webhooks, you may need to fake additional API calls that you're making to Stripe.

But, there are also a couple of other, manual, but easy ways to test. Let's check 'em out!

Leave a comment!

This tutorial uses an older version of Symfony of the stripe-php SDK. The majority of the concepts are still valid, though there *are* differences. We've done our best to add notes & comments that describe these changes.

What PHP libraries does this tutorial use?

// composer.json
    "require": {
        "php": ">=5.5.9, <7.4",
        "symfony/symfony": "3.1.*", // v3.1.10
        "doctrine/orm": "^2.5", // v2.7.2
        "doctrine/doctrine-bundle": "^1.6", // 1.6.8
        "doctrine/doctrine-cache-bundle": "^1.2", // 1.3.0
        "symfony/swiftmailer-bundle": "^2.3", // v2.6.2
        "symfony/monolog-bundle": "^2.8", // v2.12.1
        "symfony/polyfill-apcu": "^1.0", // v1.3.0
        "sensio/distribution-bundle": "^5.0", // v5.0.22
        "sensio/framework-extra-bundle": "^3.0.2", // v3.0.26
        "incenteev/composer-parameter-handler": "^2.0", // v2.1.2
        "friendsofsymfony/user-bundle": "~2.0.1", // v2.0.1
        "stof/doctrine-extensions-bundle": "^1.2", // v1.2.2
        "stripe/stripe-php": "^3.15", // v3.23.0
        "doctrine/doctrine-migrations-bundle": "^1.1", // v1.2.1
        "phpunit/phpunit": "^5.5", // 5.7.20
        "composer/package-versions-deprecated": "^1.11" // 1.11.99
    "require-dev": {
        "sensio/generator-bundle": "^3.0", // v3.1.4
        "symfony/phpunit-bridge": "^3.0", // v3.3.0
        "hautelook/alice-bundle": "^1.3", // v1.4.1
        "doctrine/data-fixtures": "^1.2" // v1.2.2