Testing the most important piece of your application
Main Thread • 6 min read
Arguably one of the most important pieces of an application is accepting payment. Within a Laravel application, you can craft a form and drop in Stripe Checkout to be accepting payments in no time.
However, testing this is a different story. Writing these tests not only takes time, but enters the quagmire of how test this flow.
Some might perform a low level unit test to confirm payment details. Some might write an integration test for the form submissions.
I think these provide a foundation. But, for the most important piece of the application I like to also write a browser test to verify the entire checkout flow.
In this post I'll share the transcript and video from Confident Laravel. Where I take the Confident Laravel application codebase from no tests to confidently tested.
This particular segment uses Laravel Dusk to test the checkout flow. This test combined with an HTTP Test for the OrderController
and a low level unit test for the PaymentGateway
leave me feeling completely confident the the most important piece of the Confident Laravel application is behaving as expected.
First I'll add the Laravel Dusk package as a development dependency: composer require --dev laravel/dusk
Then I'll install Dusk with its artisan command: php artisan dusk:install
And I'll use the dusk:make
command to generate a PurchasePackageTest
.
Taking a look at the test file we see this extends the DuskTestCase
and has provided an example test case to get started.
To follow Laravel conventions, I'll modify this to use the @test
annotation and update the name to it_can_purchase_the_starter_package_and_create_account
.
Now this test is rather complicated, so I'm not going to muddle through figuring out how to write each of the interaction steps.
Instead I'm going to do what any experienced developer would do and defer to a Google search. That's right when in doubt, Google it out.
Now I did vet these posts beforehand and this second result has an example of using Laravel Dusk and Stripe Checkout.
So let's copy their code as a foundation and we'll review each line in order to modify it for our use case.
We see that it visits the purchase page, so I'll change this to the root URL instead.
Now, it uses the default "Pay with Card" text, whereas the Confident Laravel application uses the text "Buy Now" with a price. So I'll need to clean this up.
It also presses the button. but Confident Laravel uses a link. So I'll need to change this to clickLink()
.
I could use a CSS selector. But as I mentioned before I want to leave some flexible in my tests. I don't want this test to fail just because I change the HTML markup. So using the link text gives us a bit more flexibility.
Now, you might be thinking, It's using the text just as brittle? Maybe. But it comes with the added benefit of indirectly testing the package price.
Again, it's a tradeoff to consider when testing your own application.
Now for the tricky part. We have to determine how to interact with Stipe using Dusk. This is one of the reason I Google'd the solution. Otherwise, I would need to get in the inspector to determine a lot of these details.
Let's take a quick look at this first one and then I'll move more quickly through the rest. Looking at the inspector, we see Stripe indeed loads an iframe
for its checkout form, which has the name stripe_checkout_app
.
The code waits for the iFrame to load before interacting with it. Once it's loaded, it uses Dusk to target the next set of actions within the iFrame, instead of the page.
From there we can make a quick assertion to verify the correct iFrame was loaded. In this case, verify the package name matches the name in Stripe Checkout.
This brings us to another Testing Tip.
Never hesitate to perform a sanity check.
While this assertion isn't necessary, it does confirm the linkage between Stripe Checkout and the selected package. While it should work, I don't want to assume and risk a false positive test result. This sanity check ensures the code is behaving as expected up to this point.
Knowing it's the right Stripe Checkout, we'll fill out Stripe's payment form using one of their test cards.
First, I'll generate an email address with Faker. This gives me some of that test variance I like.
The credit card number is one of Stripe's Visa test cards. But I'll remove the spaces to avoid any oddities.
Next is the card expiration date. While this is in in the future now, I don't want this test to start failing in January of 2022. So I'll add a little dynamic date math to ensure we always have a future date.
This brings us to another Testing Tip:
When testing points in time, always be in control.
The CVC code is fine and at this point we can submit the checkout form.
Then we'll wait for Stripe to complete its interaction.
When writing a Dusk test, it's a good idea to spot check the interaction. So I'm manually repeat these steps to ensure we're on the right track.
I'll enter the checkout form details as the test would. Then we would be ready to press the button. At which point we'll wait for the Stripe iframe
to close.
At which point we're redirected to the /edit/user
page.
This is the final part to add to the test case. We can do this with assertPathIs()
.
Now I think we're ready to run this Dusk test.
Unfortunately it fails with a rather misleading error. Let's look at those handy screenshots to see what it did.
Looks like it's hitting the default Apache web server page.
This is because I haven't configured the application URL. I'll change this to my local development environment and run the test again.
It still fails, but this time with a different error. Looks like the assertion failed.
Let's look at the screenshot. It appears Stripe hasn't fully finished. Looks like it's closing, but the page hasn't reloaded.
This is pretty common when writing Dusk tests. They'll often be some timing issues.
I'll prepend a waitForReload()
call and run the tests again.
This time it passes.
Let's run it once more just to confirm we didn't get lucky with the timing.
And it still passes. Good.
Let's see this in action by disabling the headless
browser configuration.
Look at it go. That's pretty awesome.
Now, these tests run pretty slow compared to some of our other tests. Taking almost 17 seconds. The reality is this is not a test I'll run very often. It's simply nice to have if I ever questioned the Stripe integration.
It's also important to note Dusk tests do not run as part of the PHPUnit test suite. So there's separation between these tests by default. Yet another benefit using a Dusk test.
Enjoy this video? Watch 31 more just like it in the Confident Laravel video course for a step-by-step guide to testing your Laravel applications.