I went from ~200 tests covering 19 controllers to 1,378 tests with 3,958 assertions covering the entire PlanPal app. What I found along the way proved the investment was already overdue.

The Bugs That Were Already There

Writing tests isn't just about preventing future bugs -- it catches existing ones:

A WebPush controller called successResponse() and errorResponse() on every endpoint -- methods that didn't exist on the base controller. Every WebPush endpoint would have returned a 500:

// Every method in WebPushController had this:
return $this->successResponse($data);  // Method doesn't exist!

// BaseController actually has:
return $this->success($data);

A Book controller had an N+1 query -- loading activeSession for each book individually instead of eager loading with ->with('activeSession').

Appointment deletion left orphaned relationship goal completions, silently inflating a user's progress counters.

Email Intelligence skipped OAuth validation when granted_scopes contained gmail.readonly, even when the actual token didn't have that scope -- causing every scan to fail with a 403.

None of these were caught by manual testing. All caught immediately by automated tests.

The Coverage

Category Tests Assertions
API Controllers (45 files) ~960 ~2,750
Services (8 files) ~180 ~400
Jobs (8 files) ~50 ~130
Commands, Observers, Auth ~188 ~678
Total 1,378 3,958

Every API controller now has tests covering happy paths, authentication (401 for unauthenticated), user isolation (User A can't access User B's data), validation, and premium gates.

The Most Important Line

// AppServiceProvider.php
Model::preventLazyLoading(! $this->app->environment('production'));

One line. In development, any accidental lazy load throws LazyLoadingViolationException. In production, it still works normally. This single addition prevents every future N+1 query from shipping unnoticed.

The ROI

Every future change runs against 1,378 tests in ~62 seconds. Every bug fix gets a regression test. Every new feature has a template to follow. The best time to write tests was when the code was first written. The second best time is now.