Lesson Summaries

Getting the Ball Rolling

  • 1. What Do We Build First?

    First of all, welcome to Test-Driven Laravel!

    To get things started, let's talk a bit about the app we are going to build, and figure out where we should start.

  • 2. Sketching out Our First Test

    In this lesson, we scaffold out a new application and start sketching out the first test.

    Important note

    We start the course with Laravel 5.3 which was the current version at the time of recording. If you want to follow along line-by-line, I recommend that you start with a fresh 5.3 project, and upgrade to 5.4 and beyond when we do it in the course.

    To create a fresh Laravel 5.3 project, run this in your terminal:

    composer create-project laravel/laravel=5.3.* ticketbeast

    If you'd like to start with Laravel 5.4 (or even 5.5), I recommend watching the upgrade videos first so you have an idea of what differences you need to watch out for.

  • 3. Getting to Green

    In this lesson, we use TDD to scaffold out a lot of the boiler plate in the application and get our first test passing.

    Links

  • 4. Unit Testing Presentation Logic

    In this lesson, we drive out some presentation helpers via unit tests to extract some logic from our view.

    Links

  • 5. Refactoring for Speed

    In this lesson, we finish extracting some presentation helpers, then refactor our tests to run without using the database to keep things fast.

  • 6. Hiding Unpublished Concerts

    In this lesson, we introduce the idea of "published" and "unpublished" concerts, and hide unpublished concerts from visitors to the site.

  • 7. Testing Query Scopes

    In this lesson, we extract some query logic to a custom scope and ensure that it's covered by our tests.

  • 8. Factory States

    In this lesson, we abstract some details about makes a concert "published" from our tests by encapsulating them in a factory state.

Purchasing Concert Tickets

Limiting Ticket Sales

  • 19. Outlining the First Test Case

    In this lesson, we work on writing a test to ensure that customers can't purchase more tickets than are still available.

  • 20. Adding Tickets to Concerts

    In this lesson, we implement the ability to add tickets to concerts so there are tickets available when a customer tries to purchase.

  • 21. Refusing Orders When There Are No More Tickets

    In this lesson, we work on making sure orders are not created when someone tries to purchase more tickets than remain, and ensuring that a customer can't purchase tickets already purchased by another customer.

  • 22. Finishing the Feature Test

    In this lesson, we get our "cannot purchase more tickets than remain" feature test passing by finishing our controller implementation.

  • 23. Cancelling Failed Orders

    In this lesson, we make sure that if a customer's payment fails, we cancel their order.

  • 24. Refactoring and Redundant Test Coverage

    In this lesson, we extract some logic from our Order class to our Ticket class, and discuss whether or not it's important to add new test coverage.

  • 25. Cleaning Up Our Tests

    Much like production code, test code needs to be refactored and kept healthy and maintainable.

    In this lesson, we go over our existing test suite and look for opportunities to clean it up.

Returning Order Details

  • 26. Asserting Against JSON Responses

    In this lesson, we add tests to ensure that we are getting back meaningful order information after purchasing tickets, and discuss different strategies for testing JSON responses.

  • 27. Returning Order Details

    In this lesson, we work on getting our existing feature test passing by driving out an order's JSON representation at the unit level.

  • 28. This Design Sucks

    You don't always write beautiful code on the first try.

    In this lesson, we talk about three issues with our existing design and why they are worth addressing.

A Surgical Refactoring

  • 29. Persisting the Order Amount

    In this lesson, we work on persisting the order amount to the database, instead of calculating it on the fly based on ticket price and ticket quantity.

  • 30. Removing the Need to Cancel Orders

    In this lesson, we work getting rid of the need to cancel orders when a payment fails by separating the idea of finding tickets from confirming an order.

  • 31. Preparing for Extraction

    In this lesson, we work on removing the need to create orders through a concert by removing a redundant relationship.

  • 32. Extracting a Named Constructor

    In this lesson, we introduce a named constructor for creating orders from customer details and tickets.

  • 33. Precomputing the Order Amount

    In this lesson, we work on removing the need for an order to know how to calculate it's own price so we can eliminate some duplication in our code.

  • 34. Uncovering a New Domain Object

    In this lesson, we walk through a strategy I use to discover new objects in my code, and start driving out a new domain object with tests.

  • 35. You Might Not Need a Mocking Framework

    In this lesson, we work on removing the need to migrate the database when testing our simple Reservation class.

    We also talk about different approaches to isolating the Reservation from it's collaborators, and why you might not need a special library to do it.

Off to the Races

  • 36. Uh Oh, a Race Condition!

    Over the last few lessons we've improved the design of our code significantly, but we've also introduced a race condition.

  • 37. Requestception

    In this lesson, we discuss how subrequests work, and how we can use them to help test this race condition.

  • 38. Hooking into Charges

    In this lesson, we add a hook to our fake payment gateway to allow us to trigger a nested request.

  • 39. Uh Oh, a Segfault!

    In this lesson, we finish writing our feature test to cover the race condition. But when we run it, we hit a segfault! Let's see if we can diagnose the issue.

  • 40. Replicating the Failure at the Unit Level

    In order to get a better understanding of our segfault issue, we try and replicate it at the unit level so we can fix it more easily.

  • 41. Reserving Individual Tickets

    To make sure nobody can purchase someone else's tickets while they are still trying to pay, we work on introducing the idea of a ticket being "reserved."

  • 42. Reserved Means Reserved!

    It this lesson, we work on making sure the rest of the application respects our newly introduced "reserved" status on tickets.

  • 43. That Guy Stole My Tickets!

    For some reason, person B is getting their name added to tickets that person A paid for! Let's figure out what's going on.

Hunting for Stale Code

  • 44. Cancelling Reservations

    In this lesson, we find a sneaky bug and work towards solving it through an isolated unit test.

    Links

  • 45. Refactoring Mocks to Spies

    In this lesson, we talk about 3 different ways to create mock objects, as well as how using spies instead of mocks can help keep your tests more organized.

  • 46. A Change in Behavior

    Our feature test is still failing because some of our older code has a different understanding of what it means to "release" tickets.

    In this lesson, we diagnose the issue and update our specification to help us drive out the change in behavior.

  • 47. Deleting Stale Tests

    In this lesson we discuss the importance of high level feature tests, and how they help give us the confidence to delete stale code when confronted with a failing unit test.

Something Smells in Our Controller

  • 48. Cleaning up a Loose Variable

    In this lesson, we look to eliminate a loose variable in our controller by adding some additional behavior to our Reservation class, and then spend some time pushing the responsibility for creating reservations inside our Concert class and our of our controller.

  • 49. Moving the Email to the Reservation

    In this lesson, we notice a long parameter list we'd like to refactor, but in order to do that, we need to tweak how reservations are created so that the reservation has knowledge of the customer who is reserving the tickets.

  • 50. Refactoring "Long Parameter List" Using "Preserve Whole Object"

    In this lesson, we attempt to refactor the "long parameter list" code smell we noticed in our Order class using the "preserve whole object" refactoring.

    Links

  • 51. Green with Feature Envy

    Our "preserve whole object" refactoring didn't turn out as nicely as we hoped.

    In this lesson, we look to treat the original problem as a "feature envy" issue instead of a "long parameter list" issue, and see if that leads us to a better solution.

    Links

  • 52. Avoiding Service Classes with Method Injection

    In this lesson, we notice that we're lacking a single source of truth for the amount we charge the customer and the amount used to create the new order.

    We walk through what it might look like to solve this problem with a service class, and then how we can avoid the service class entirely using method injection.

Testing-Driving the Real Stripe Adapter

  • 53. Generating a Valid Payment Token

    In this lesson we scaffold out an initial test for our StripePaymentGateway and figure out how to generate a valid payment token using Stripe's API.

  • 54. Retrieving the Last Charge

    In this lesson we use Stripe's API to fetch the most recent charge so we can make assertions about it in our test.

  • 55. Making a Successful Charge

    In this lesson we work through the first set of test failures and successfully make a charge to Stripe.

  • 56. Dealing with Lingering State

    In this lesson we diagnose why are test is passing after commenting out our implementation, and come up with a new strategy to run our tests with isolated data.

    Errata

    Two small corrections were made to the code in the video before committing:

    1. Switching to array_first

    In the lastCharge method, we were trying to access the element at offset 0 to get the most recent charge:

    private function lastCharge()
    {
        return \Stripe\Charge::all(
            ['limit' => 1],
            ['api_key' => config('services.stripe.secret')]
        )[data][0];
    }

    This will throw an error if you don't happen to have any charges created yet.

    Instead, use array_first to get the first charge or null if no charges are present:

    private function lastCharge()
    {
        return array_first(\Stripe\Charge::all(
            ['limit' => 1],
            ['api_key' => config('services.stripe.secret')]
        )['data']);
    }

    The newCharges method was also updated to check for null before trying to get the id of the last charge:

    private function newCharges()
    {
        return \Stripe\Charge::all(
            [
                'limit' => 1,
                'ending_before' => $this->lastCharge ? $this->lastCharge->id : null,
            ],
            ['api_key' => config('services.stripe.secret')]
        )['data'];
    }

    2. Removing the limit

    When copying over the newCharges implementation, we mistakenly left in the limit parameter:

    private function newCharges()
    {
        return \Stripe\Charge::all(
            [
                'limit' => 1,
                'ending_before' => $this->lastCharge ? $this->lastCharge->id : null,
            ],
            ['api_key' => config('services.stripe.secret')]
        )['data'];
    }

    This was removed before committing to make sure we always get all the charges back that were created in the test, and not just the most recent one:

    private function newCharges()
    {
        return \Stripe\Charge::all(
            [
                'ending_before' => $this->lastCharge ? $this->lastCharge->id : null,
            ],
            ['api_key' => config('services.stripe.secret')]
        )['data'];
    }
  • 57. Don't Mock What You Don't Own

    A common misconception about testing is that you should mock calls to external APIs to avoid the network.

    In this lesson, we compare the pros and cons of integrating with Stripe vs. mocking our calls to Stripe to better understand why shouldn't mock third-party code.

  • 58. Using Groups to Skip Integration Tests

    In this lesson, we use PHPUnit's "group" feature to make it easy to skip our integration tests when we don't have an internet connection.

  • 59. Handling Invalid Payment Tokens

    In this lesson we add a new test to make sure our StripePaymentGateway behaves as expected when attemping to charge with an invalid payment token.

  • 60. The Moment of Truth

    In this lesson, we finally fire up the browser to find out if all of this TDD stuff has really given us a working system.

Keeping Things Synchronized with Contract Tests

  • 61. When Interfaces Aren't Enough

    In this lesson, we identify some of the risks of using fakes, and why it takes more than an interface to make sure multiple implementations stay in sync.

  • 62. Refactoring Towards Duplication

    In this lesson, we begin refactoring the tests for our two PaymentGateway implementations towards being identical so we can extract them to a contract test.

  • 63. Capturing Charges with Callbacks

    In this lesson, we come up with a strategy for being able to keep track of the total charges made during a test that will work for both implementations of our PaymentGateway interface.

  • 64. Making the Tests Identical

    In this lesson, we port the newChargesDuring method over to the FakePaymentGateways, finally leaving us with two identical tests that are ready for extraction.

  • 65. Extracting a Contract Test

    In this lesson, we extract the identical PaymentGateway tests into a contract test, shared through a trait.

  • 66. Extracting the Failure Case

    In this lesson, we refactor the charges_with_an_invalid_payment_token_fail tests to make them identical, and then extract it to our contract test.

Upgrading Our Suite to Laravel 5.4

Viewing Order Confirmations

  • 69. Sketching out Order Confirmations

    In this lesson, we begin driving out our test for viewing order confirmations based on a static mockup.

  • 70. Driving out the Endpoint

    In this lesson, we figure out the URL structure we want to use to keep order confirmation pages private, and drive out the ability to successfully hit out new endpoint.

  • 71. Asserting Against View Data

    In this lesson, we flesh out an initial implementation of our controller, and walk through how to test the data that is bound to a view without asserting against the rendered HTML.

  • 72. Extracting a Finder Method

    In this lesson, we refactor some Eloquent calls in our controller to a dedicated finder method, and discuss when it's important to add new tests while refactoring and when it's not.

  • 73. Making Static Data Real

    In this lesson, we work through using tests to replace some of the hard coded data in our mockup with real data that's attached to our order.

  • 74. Deciding What to Test in a View

    In this lesson, we spend some time discussing what you need to take into consideration when deciding what data is important to test in a rendered view.

  • 75. Decoupling Data from Presentation

    In this lesson, we talk about strategies you can use to make your feature tests more resilient to superficial changes in your templates, and work through a specific example using dates.

Generating Confirmation Numbers

  • 76. Fixing the Test Suite

    In this lesson, we get our test suite back to green by temporarily making some of our new columns nullable, and discuss adding a test to ensure confirmation numbers are generated for new orders.

  • 77. Stubbing the Interface

    In this lesson, we decide we want to be able to stub how order confirmation numbers are generated for our high level feature test, and use that insight to design an interface where we can encapsulate that logic.

  • 78. Updating Our Unit Tests

    In this lesson, we update one of our Order unit tests to make use of the factory we added previously, and make sure that orders include their confirmation numbers when we render them as JSON.

  • 79. Confirmation Number Characteristics

    In this lesson, we discuss the what we want our confirmation numbers to ultimately look like and why.

  • 80. Testing the Confirmation Number Format

    In this lesson, we use the characteristics we identified previously to specify how our confirmation numbers should look with a set of unit tests, and work on getting them to pass.

  • 81. Ensuring Uniqueness

    In this lesson, we come up with a strategy to force ourselves away from our slimed implementation by testing that each confirmation number is unique.

  • 82. Refactoring to a Facade

    In this lesson, we wire up our RandomOrderConfirmationNumberGenerator to be the default implementation we use in our application, and make use of Laravel's Facades to replace our explicit use of Laravel's container in our Order class with something more expressive.

Storing the Last Four Card Digits

  • 83. Promoting Charges to Objects

    In this lesson, we introduce a new Charge object to carry meta data about purchases, such as the last four digits of the card used. We drive out this change in our FakePaymentGateway first through changes to our PaymentGatewayContractTests.

  • 84. Leveraging Our Contract Tests

    In this lesson, we use our updated contract tests to update the behavior of our StripePaymentGateway to keep it synchronized with our fake.

  • 85. Storing Charge Details with Orders

    In this lesson, we update our OrderTest to account for creating orders using Charges instead of plain amounts, and use that test to drive out the implementation in Order.

  • 86. Deleting More Stale Code

    In this lesson, we notice that changing how Orders are created reveals some lingering old design decisions that are triggering test failures. We work through removing the stale code, and updating any tests we need to keep to no longer rely on the code we want to remove.

Assigning Ticket Codes

  • 87. Feature Test and JSON Updates

    In this lesson, we come up with approach for driving out the generation of ticket codes from the outside in, and make some tweaks to the JSON representation of a completed order.

  • 88. Claiming Tickets When Creating Orders

    In this lesson, we decide when ticket codes should be generated, and rework an existing test to use mock expectations to prepare for our implementation.

  • 89. Assigning Codes When Claiming Tickets

    In this lesson, we add a new test for the new claimFor method in our Ticket class, and drive out the implementation.

  • 90. The Birthday Problem

    In this lesson, we talk about the birthday problem and the implications it has on how we generate ticket codes.

    We also play with the Hashids library to get an understanding of how we could use it to encode ticket IDs.

    Links

  • 91. Integrating Hashids

    In this lesson, we drive out our HashidsTicketCodeGenerator, making sure that ticket codes are generated in the format we expect.

  • 92. Dealing with Out of Sync Mocks

    Uh oh! One of our mock expectations is no longer in sync with the real implementation.

    In this lesson, we use an underappreciated Mockery feature to detect broken mocks, and get things working properly again.

  • 93. Wiring It All Together

    In this lesson, we bubble back up to our initial feature test and try to get it passing.

    We cover how to return multiple values from a Mockery stub, and wire up our real HashidsTicketCodeGenerator in the IOC container to get the test suite back to green.

  • 94. Ready to Demo

    In this short lesson, we make a small tweak to our TicketCheckout Vue component and finally walk through the entire purchasing flow from start to finish.

Emailing Order Confirmations

Logging in with Dusk

  • 98. Testing the Login Endpoint

    The next thing we'd like to build is the ability for promoters to add new concerts, but before we can do that, we need to give them a way to log in to the application.

    In this lesson, we drive out our initial /login endpoint.

  • 99. Should You TDD Simple Templates?

    In the last lesson we drove out the login endpoint, but we still don't have an actual login form.

    Is this something we should try to build with TDD, or is there a better approach?

  • 100. Namespacing Our Test Suite

    Before we get started with Laravel Dusk, let's update our test folder structure to match what ships with Laravel 5.4 to make it a little easier to integrate.

  • 101. Getting Started with Laravel Dusk

    In this lesson, we install and configure Laravel Dusk, talk about some gotchas you might run into if you're not approaching it with the right mindset, and get a basic example browser test running.

    Notes

    We're using Laravel Dusk ^1.1 in this video and ^2.0 doesn't support Laravel 5.4, so if you're following along in 5.4, you'll want to install ^1.1 explicitly:

    composer require laravel/dusk:^1.1
  • 102. QA Testing the Login Flow

    In this lesson, we use Dusk to drive out a browser test for our login form to make sure we have regression coverage.

Adding New Concerts

  • 103. Preventing Guests from Adding Concerts

    In this lesson, we walk through some behind-the-scenes changes made since the last lesson and work on adding some basic authorization tests.

  • 104. Adding a Valid Concert

    In this lesson we drive out the ability to add new concerts and make sure only promoters can access this endpoint.

  • 105. Validation and Redirects

    In this lesson we drive out our first validation rule for the concert form, and look at a useful trick for making assertions about redirect()->back() behavior.

    Notes

    In Laravel v5.5.19, the custom from helper we added in this lesson was added natively, so it's not necessary to add it yourself as long as you upgrade to the latest version of Laravel.

  • 106. Converting Empty Strings to Null

    In this lesson we add a test to prove that concert subtitles are optional, and active some new middleware from Laravel 5.4 to simplify the task.

  • 107. Reducing Noise with Form Factories

    In this lesson we identify a lot of duplication in our validation tests and use a technique similar to model factories to clean up the noise.

  • 108. Connecting Promoters to Concerts

    Up until now we've been operating under the assumption that we'd add concerts manually on a promoter's behalf.

    Now that promoters can add concerts themselves, we need to make sure we are tracking who adds each concert so we know who to transfer the ticket money to.

  • 109. Autopublishing New Concerts

    In one of the earliest lessons in the course, we added some functionality to make sure that only published concerts were visible in the browser.

    Since we don't have the ability to publish new concerts from the UI yet, let's at least make sure that new concerts are published by default until we get to that feature.

Listing a Promoter's Concerts

  • 110. Asserting Against View Objects

    In this lesson we start driving out tests for a concert index page. We also talk about an incredibly useful strategy for making assertions about views without dealing with HTML.

  • 111. Avoiding Sort-Sensitive Tests

    In this lesson I outline a small sort-order related trap you can fall into if you're not careful when asserting against lists of items, and talk about a strategy for avoiding it.

  • 112. Refactoring Assertions with Macros

    In this lesson we work through using Laravel's macro feature to create more expressive custom assertions.

Updating Basic Concert Info

  • 113. Viewing the Update Form

    In this lesson, I walk through some simple tests I put together behind-the-scenes to give us a head start on updating concerts.

    Notes

    If you're building along in your own repo, be sure to go through the changes made in this lesson's commit and apply them to your own project.

    Most notably, we've moved the $response->data(...) macro to the base TestCase class so we can reuse it in our other tests.

  • 114. The First Update Test

    In this lesson, we drive out the first test we need for editing concert details, and talk through a useful naming strategy to use when testing for attribute changes.

  • 115. Driving Out Basic Concert Updates

    In this lesson we implement the ability perform concert updates, and start working through some of the permission checks we need to be concerned with.

  • 116. Restricting Updates to Unpublished Concerts

    In this lesson we make sure only unpublished concerts can be edited, prevent guests from editing concerts, and drive out an example validation rule.

    Notes

    In Laravel v5.5.19, the custom from helper we've been using in our tests was added natively, so rather than moving it to the base test case, feel free to run composer update to pull in the latest version of Laravel and delete that helper entirely.

Postponing Ticket Creation

Publishing Concert Drafts

Building the Sales Dashboard

  • 127. Calculating Tickets Sold

    In this lesson walk through a few behind-the-scenes changes, then drive out a method for calculating tickets sold.

  • 128. Making the Progress Bar Work

    In this lesson we add some new methods for calculating the percentage of tickets sold, and touch on a neat tip for making assertions about floating point numbers.

  • 129. Total Revenue and a Relationship Bug

    In this lesson we work on calculating the total revenue for a concert, but run into an interesting bug we need to tackle with our $concert->orders relationship.

Listing Recent Orders

  • 130. Creating a Custom OrderFactory

    In this lesson we start working on making it possible for promoters to view a list of recent orders. When we hit a snag with some complex factory setup, we work on extracting a custom factory class to simplify our code.

  • 131. Asserting Against Sort Order

    In this lesson we add the assertions we need to verify that our recent orders are getting passed to the view in the right order, and discuss why asserting against view data makes this so much easier than it would be if we were asserting against HTML.

  • 132. Splitting Large Tests

    In this lesson we talk about when, why, and how to split up a large test into many.

Queuing Mass Attendee Emails

Upgrading to Laravel 5.5

Uploading Concert Posters

  • 139. Faking Uploads and File Systems

    In this lesson we write our first test for uploading concert images, and walk through how to attach files to requests in feature tests, as well as how to use Laravel's built-in file system fake to make assertions about file uploads.

  • 140. Storing Files and Comparing Content

    In this lesson we get our initial file upload test passing, and also talk about how to verify that the stored file contents match the uploaded file contents.

  • 141. Validating Poster Images

    In this lesson we add additional tests for validating the file type, dimensions, and aspect ratio of uploaded concert posters.

  • 142. Optional Files and the Null Object Pattern

    In this lesson we add a test to make sure that concert posters are optional, and come up with a clean implementation using the Null Object pattern.

Optimizing Poster Images

Inviting Promoters and Accepting Invitations

Automating Payouts with Stripe Connect

  • 156. Getting Cozy with Stripe Connect

    In this lesson, we learn about how Stripe Connect works and drive out an initial test for the OAuth redirect flow using Laravel Dusk.

  • 157. Authorizing with Stripe

    In this lesson we implement the first half of the Stripe Connect redirect flow, where we send promoters to Stripe to connect their account.

  • 158. Exchanging Tokens

    In this lesson we handle the second half of the redirect flow; exchanging the temporary code Stripe gives us for a valid access token that we can use to make requests on behalf of a promoter.

  • 159. Unit Testing Middleware

    In this lesson we work on driving out a middleware that will force promoters to connect a Stripe account.

  • 160. Testing Callbacks with Invokables

    In this lesson we make sure our middleware doesn't block promoters who have already connected their Stripe accounts, covering an approach for testing interfaces that accept callbacks using invokable classes.

  • 161. Testing That Middleware Is Applied

    In this lesson we discuss and implement a strategy for making sure that our new middleware is applied to the correct routes.

  • 162. Updating Factories and a Speed Trick

    In this lesson we check if our new middleware has had any impact on the rest of our test suite, and walk through a quick trick that can dramatically speed up your test suite.

  • 163. Total Charges for a Specific Account

    In this lesson we start working on sending payments directly to promoters by making sure we can verify which accounts received which payments.

  • 164. Paying Promoters Directly

    In this lesson we make the necessary changes to our application code to make sure that when a payment is processed it's sent to the promoter's account instead of the application account.

  • 165. Splitting Payments with Stripe

    In this lesson we update our StripePaymentGateway to match our new payment gateway contract, and add an additional test to make sure that payments processed by Stripe are split as expected between the application account and the promoter's account.

  • 166. It's Alive!

    In the final lesson of Test-Driven Laravel, we do one last demo of the ticket purchasing process from inviting a promoter to receiving an order confirmation, and double check that everything really is working as expected via the Stripe dashboard.