Most developers know that testing your code is A Good Thing. We’re supposed to do it. We likely have an idea of why it’s good, and we might’ve even read some tutorials about how it’s supposed to work.
But the gap between knowing why you should test and knowing how to test is wide. Thankfully, tools like PHPUnit, Mockery, and PHPSpec have provided an incredible number of options for testing in PHP—but it can still be pretty overwhelming to get everything set up.
Out of the box, Laravel comes with baked-in integrations to PHPUnit (unit testing), Mockery (mocking), and Faker (creating fake data for seeding and testing). It also provides its own simple and powerful suite of application testing tools, which allow you to “crawl” your site’s URIs, submit forms, check HTTP status codes, and validate and assert against JSON. It also provides a robust frontend testing framework called Dusk that can even interact with your JavaScript applications and test against them. In case this hasn’t made it clear, we’re going to cover a lot of ground in this chapter.
To make it easy for you to get started, Laravel’s testing setup comes with sample application test that can run successfully the moment you create a new app. That means you don’t have to spend any time configuring your testing environment, and that’s one less barrier to writing your tests.
Tests in Laravel live in the tests folder. There are two files in the root: TestCase.php, which is the base root test which all of your tests will extend, and CreatesApplication.php, a trait (imported by TestCase.php) which allows any class to boot a sample Laravel application for testing.
You also have a folder for “Feature tests,” or tests which cover the broader interaction of multiple units, and “Unit tests”, which are intended to cover just one unit (class, module, function, etc.). Each of these folders contains an ExampleTest.php file, each of which have a single sample test inside them, ready to run.
In projects running versions of Laravel prior to 5.4, there will be only two files in the tests directory: ExampleTest.php, your sample test, and TestCase.php, your base test.
Additionally, if your app is pre-5.4, the syntax in all of the examples below will not be quite right. All the ideas are the same, but the syntax will be just a bit different across the board. You can learn more at the Laravel Docs for testing in 5.3 and before. Here are the four biggest changes:
In 5.3 and before, you’re not creating response objects; instead, you’re just calling methods on $this, and the test class stores the responses. So $response = $this->get('people’) in 5.4+ would look like $this->get('people’) in 5.3-.
Many of the assertions have been renamed in small ways in 5.4+ to make them look more like PHPUnit’s normal assertion names; e.g. assertSee instead of see.
Some of the “crawling” methods that in 5.4+ have been extracted out to browser-kit-testing were built into the core in 5.3-.
Dusk didn’t exist prior to 5.4.
Because testing prior to 5.4 was so different, I’ve made the testing chapter for the first edition of this book available as a free PDF. If you’re working with 5.3 or earlier, I’d recommend skipping this chapter in the book and using this PDF of the testing chapter from the first edition instead.
The ExampleTest in your Unit directory just has a simple assertion: $this->assertTrue(true). Anything in your unit tests is likely to be relatively simple PHPUnit syntax (asserting that values are equal or different, looking for entries in arrays, checking booleans, etc.), so there’s not much to learn there.
If you’re not yet familiar with PHPUnit, most of our assertions will be run on the $this object with this syntax:
$this->assertWHATEVER($expected,$real);
So, for example, if we’re asserting that two variables should be equal, we’ll pass it first our expected result, and second the actual outcome of the object or system we’re testing.
$multiplicationResult=$myCalculator->multiply(5,3);$this->assertEqual(15,$multiplicationResult);
As you can see in Example 12-1, the ExampleTest in the Feature directory makes a simulated HTTP request to the page at the root path of your application and checks that its HTTP status is 200 (successful). If it is, it’ll pass; if not, it’ll fail. Unlike your average PHPUnit test, we’re running these assertions on the TestResponse object that’s returned when we make test HTTP calls.
<?phpnamespaceTests\Feature;useTests\TestCase;useIlluminate\Foundation\Testing\RefreshDatabase;classExampleTestextendsTestCase{/*** A basic test example.** @return void*/publicfunctiontestBasicTest(){$response=$this->get('/');$response->assertStatus(200);}}
To run the tests, run ./vendor/bin/phpunit on the command line from the root folder of your application. You should see something like the output in Example 12-2.
PHPUnit 7.3.5 by Sebastian Bergmann and contributors. ..2/2(100%)Time:139ms, Memory: 12.00MB OK(2test,2assertions)
You just ran your first Laravel application test! As you can see, you’re set up out of the box not only with a functioning PHPUnit instance, but also a full-fledged application testing suite that can make mock HTTP calls and test your application’s responses. Further, you’ll soon learn that you have easy access to a fully-featured DOM crawler (“A quick intro to BrowserKit Testing”) and a regression testing tool with full JavaScript support (“Testing with Dusk”).
In case you’re not familiar with PHPUnit, let’s take a look at what it’s like to have a test fail. Instead of modifying that test, we’ll make our own. Run php artisan make:test FailingTest. This will create a file in tests/Feature/FailingTest.php; you can modify its testExample() method to the following:
publicfunctiontestExample(){$response=$this->get('/');$response->assertStatus(301);}
As you can see, it’s the exact same as our example test, but we’re now testing against the wrong status. Let’s run PHPUnit again.
If you want your test to be generated in the Unit directory instead of the Feature directory, pass the --unit flag:
php artisan make:test SubscriptionTest --unit
Whoops! This time the output will probably look a bit like Example 12-4.
PHPUnit 7.3.5 by Sebastian Bergmann and contributors. .F. 3 / 3 (100%) Time: 237 ms, Memory: 12.00MB There was 1 failure: 1) Tests\Feature\FailingTest::testExample Expected status code 301 but received 200. Failed asserting that false is true. /path-to-your-app/vendor/.../Foundation/Testing/TestResponse.php:124 /path-to-your-app/tests/Feature/FailingTest.php:20 FAILURES! Tests: 3, Assertions: 3, Failures: 1.
Let’s break this down. First, we now see a new “result” in the shortcut results (which were, last time, just two dots.) But this new result is an F instead of a ..
Then, for each error, it shows us the test name (here, FailingTest::testExample), the error message (Expected status code...), and a full stack trace of our error, so we can see what was called. Since this was an application test, the stack trace just shows us that it was called via the TestResponse class, but if this were a unit or feature test, we’d see the entire call stack of the test.
Now that we’ve run both a passing test and a failing test, it’s time for you to learn more about Laravel’s testing environment.
By default, Laravel’s testing system will run any files in the tests directory whose names end with the word Test. That’s why tests/ExampleTest.php was run by default.
If you’re not familiar with PHPUnit, you might not know that only the methods in your tests with names that start with the word test will be run—or methods with a @test docblock. See Example 12-5 for which methods will and won’t run.
classNamingTest{publicfunctiontest_it_names_things_well(){// Runs as "It names things well"}publicfunctiontestItNamesThingsWell(){// Runs as "It names things well"}/** @test */publicfunctionit_names_things_well(){// Runs as "It names things well"}publicfunctionit_names_things_well(){// Doesn't run}}
Any time a Laravel application is running, it has a current “environment” name that represents the environment it’s running in. This name may be set to local, staging, production, or anything else you want. You can retrieve this by running app()->environment(), or you can run something like if (app()->environment('local')) to test whether the current environment matches the passed name.
When you run tests, Laravel automatically sets the environment to testing. This means you can test for if (app()->environment('testing')) to enable or disable certain behaviors in the testing environment.
Additionally, Laravel doesn’t load the normal environment variables from .env for testing. If you want to set any environment variables for your tests, edit phpunit.xml and, in the <php> section, add a new <env> for each environment variable you want to pass in—for example, <env name="DB_CONNECTION" value="sqlite"/>.
Before we get into the methods you can use for testing, you need to know about the four testing traits you can pull into any test class.
Illuminate\Foundation\Testing\RefreshDatabase is imported at the top of every newly-generated test file, and it’s the most commonly-used database migration trait (we’ll cover the two less-common options later).
The point of this, and the other database traits, is to ensure your database tables are correctly migrated at the start of each test.
RefreshDatabase takes two steps to do this. First, it runs your migrations on your test database once at the beginning of each test run (not the individual test methods, but just when you’re running phpunit). And second, it wraps each individual test method in a database transaction, and rolls back the transaction at the end of the test.
That means you have your database migrated for your tests, cleared out fresh after each test runs, and all this without having to run your migrations again before every test, making it the fastest possible option. When in doubt, stick with this.
If you import Illuminate\Foundation\Testing\WithoutMiddleware into your test class, it will disable all middleware for any test in that class. This means you won’t have to worry about the authentication middleware, or CSRF protection, or anything else that might be useful in the real application but distracting in a test.
If you’d like to disable middleware for just a single method, instead of the entire test class, call $this->withoutMiddleware() at the top of the method for that test.
If you import the Illuminate\Foundation\Testing\DatabaseMigrations trait instead of the RefreshDatabase trait, it will run your entire set of database migrations fresh before each test. Laravel makes this happen by running php artisan migrate:fresh in the setUp() method before every test runs.
Illuminate\Foundation\Testing\DatabaseTransactions, on the other hand, expects your database to be properly migrated before your tests start. Then, it wraps every test in a database transaction, which it rolls back at the end of each test. This means that, at the end of each test, your database will be returned to the exact same state it was in prior to the test.
RefreshDatabase is only available in projects running Laravel 5.5 and later.
With simple unit tests, you almost don’t need any of these traits. You may reach for database access or inject something out of the container, but it’s very likely that unit tests in your applications won’t rely on the framework very much. Take a look at Example 12-6 for an example.
classGeometryTestextendsTestCase{publicfunctiontest_it_calculates_area(){$square=newSquare;$square->sideLength=4;$calculator=newGeometryCalculator;$this->assertEquals(16,$calculator->area($square));}
Obviously, this is a bit of a contrived example. But you can see here that we’re testing a single class (GeometryCalculator) and its single method (area()), and we’re doing so without worrying about the entire Laravel application.
Some unit tests might be testing something that technically is connected to the framework—for example, Eloquent models—but you can still test them without worrying about the framework. For example, in Example 12-7, we’ll use Package::make() instead of Package::create() so the object is created and evaluated in memory without ever hitting the database.
classPopularityTestextendsTestCase{useRefreshDatabase;publicfunctiontest_votes_matter_more_than_views(){$package1=Package::make(['votes'=>1,'views'=>0]);$package2=Package::make(['votes'=>0,'views'=>1]);$this->assertTrue($package1->popularity>$package2->popularity);}
Some people may call this an integration or feature test, since this “unit” will likely touch the database in actual usage and it’s connected to the entire Eloquent codebase. The most important point is that you can have simple tests that test a single class or method, even when the objects under test are framework-connected.
All of this said, it’s still going to be more likely that your tests—especially as you first get started—are broader and more at the “application” level. Accordingly, for the rest of the chapter we’re going to dig deeper into application testing.
In Laravel’s default ExampleTest (tests/Feature/ExampleTest.php) you can see that, with a few lines of code, we can “request” URIs in our application and actually check the status of the the response. But how can PHPUnit request pages as if it were a browser?
Any application tests should extend the TestCase class (tests/TestCase.php) that’s included with Laravel by default. Your application’s TestCase class will extend the abstract Illuminate\Foundation\Testing\TestCase class, which brings in quite a few goodies.
The first thing the two TestCase classes (yours and its abstract parent) do is handle booting the Illuminate application instance for you, so you have a fully bootstrapped application available. They also “refresh” the application between each test, which means they’re not entirely re-creating the application between tests, but rather making sure you don’t have any data lingering.
The parent TestCase also sets up a system of hooks that allow callbacks to be run before and after the application is created, and imports a series of traits that provide you with methods for interacting with every aspect of your application. These traits include InteractsWithContainer, MakesHttpRequests, InteractsWithConsole, and more, and they bring in a broad variety of custom assertions and testing methods.
As a result, your application tests have access to a fully bootstrapped application instance, application-test-minded custom assertions, with a series of simple and powerful wrappers around each to make them easy to use.
That means you can write $this->get('/')->assertStatus(200) and know that your application is actually behaving as if it were responding to a normal HTTP request, and that the response is being fully generated and then checked as a browser would check it. It’s pretty powerful stuff, considering how little work you had to do to get it running.
Let’s take a look at our options for writing HTTP-based tests. You’ve already seen $this->get('/'), but let’s dive deeper into how you can use that call, how you can assert against its results, and what other HTTP calls you can make.
At the very basic level, Laravel’s HTTP testing allows you to make simple HTTP requests (GET, POST, etc.) and then make simple assertions about their impact or response.
There are more tools we’ll cover later (BrowserKit Testing in “A quick intro to BrowserKit Testing” and Dusk in “Testing with Dusk”) that allow for more complex page interactions and assertions, but let’s start at the base level. Here are the calls you can make:
$this->get($uri, $headers = [])
$this->post($uri, $data = [], $headers = [])
$this->put($uri, $data = [], $headers = [])
$this->patch($uri, $data = [], $headers = [])
$this->delete($uri, $data = [], $headers = [])
These methods are all the basis of the HTTP testing framework. Each takes a least a URI (usually relative) and headers, and all but get() also allow for passing data along with the request.
And, importantly, each returns a $response object that represents the HTTP response. This response object is almost exactly the same as an Illuminate response object, the same thing we return out of our controllers. However, it’s actually an instance of Illuminate\Foundation\Testing\TestResponse, which wraps a normal Response with some assertions for testing.
Take a look at Example 12-8 to see a common usage of post() and a common response assertion.
publicfunctiontest_it_stores_new_packages(){$response=$this->post(route('packages.store'),['name'=>'The greatest package',]);$response->assertOk();}
In most examples like Example 12-8, you’ll also test that the record exists in the database and shows up on the index page and maybe that it doesn’t test successfully unless you define the package author and also if you’re logged in. But don’t worry, we’ll get to all of that. For now, you can make calls to your application routes with many different verbs and make assertions against both the response and the state of your application afterward. Great!
You can also make all of the same sort of HTTP tests with your JSON APIs. There are convenience methods for that, too:
$this->getJson($uri, $headers = [])
$this->postJson($uri, $data = [], $headers = [])
$this->putJson($uri, $data = [], $headers = [])
$this->patchJson($uri, $data = [], $headers = [])
$this->deleteJson($uri, $data = [], $headers = [])
These methods work just the same as the normal HTTP call methods, except they also add JSON-specific Accept, CONTENT_LENGTH, and CONTENT_TYPE headers. Take a look at Example 12-9 to see an example.
publicfunctiontest_the_api_route_stores_new_packages(){$response=$this->postJSON(route('api.packages.store'),['name'=>'The greatest package',],['X-API-Version'=>'17']);$response->assertOk();}
There are 38 assertions available on the $response object, so I recommend you take a look at all of them in the testing docs. Let’s look at a few of the most important and most common.
$response->assertOk()Asserts that the response’s status code is 200.
$response=$this->get('terms');$response->assertOk();
$response->assertStatus($status)Asserts that the response’s status code is equal to the provided $status.
$response=$this->get('admin');$response->assertStatus(401);// Un-authorized
$response->assertSee($text) and $response->assertDontSee($text)Asserts that the response contains (or doesn’t contain) the provided text.
$package=factory(Package::class)->create();$response=$this->get(route('packages.index'));$response->assertSee($package->name);
$response->assertJson(array $json, $strict = false)Asserts that the passed array is represented (in JSON format) in the returned JSON. The $strict parameter makes for a === comparison instead of a == comparison, which, to be honest, I have never used.
$this->postJson(route('packages.store'),['name'=>'GreatPackage2000']);$response=$this->getJson(route('packages.index'));$response->assertJson(['name'=>'GreatPackage2000']);
$response->assertViewHas($key, $value = null)Asserts that the view on the visited page had a piece of data available at ${$key}, and optionally it checks that the value of that variable was $value.
$package=factory(Package::class)->create();$response=$this->get(route('packages.show'));$response->assertViewHas('name',$package->name);
$response->assertSessionHas($key, $value = null)Asserts that the session has data set at $key, and it optionally checks that the value of that data is $value.
$response=$this->get('beta/enable');$response->assertSessionHas('beta-enabled',true);
$response->assertSessionHasErrors()With no parameters, asserts that there’s at least one error set in Laravel’s special errors session container. Its first parameter can be an array of key/value pairs that define the errors that should be set and its second parameter can be the string format that the checked errors should be formatted against, as demonstrated here:
// assuming the "/form" route requires an email field, and we're// posting an empty submission to it to trigger the error$response=$this->post('form',[]);$response->assertSessionHasErrors();$response->assertSessionHasErrors(['email'=>'The email field is required.']);$response->assertSessionHasErrors(['email'=>'<p>The email field is required.</p>'],'<p>:message</p>');
If you’re working with named error bags, you can pass the error bag name as the third parameter.
$response->assertCookie($name, $value = null)Asserts that the browser has been sent a cookie with name $name and, optionally, checks that its value is $value.
$response=$this->post('settings',['dismiss-warning']);$response->assertCookie('warning-dismiss',true);
$response->assertRedirect($uri)Asserts that the requested route returns a redirect to the given URI.
$response=$this->post(route('packages.store'),['email'=>'invalid']);$response->assertRedirect(route('packages.create'));
For each of these assertions, you can assume that there are many related assertions I haven’t listed here. For assertSessionHasErrors there’s also assertSessionHasNoErrors, and assertSessionHasErrorsIn. For assertJson there’s also assertJsonCount, assertJsonFragment, assertJsonMissing, assertJsonMissingExact, assertJsonStructure, and assertJsonValidationErrors. So, again, take a look at the docs and make yourself familiar with the whole list.
One piece of your application it’s common to test with application tests is authentication and authorization. Most of the time your needs will be met with the actingAs() chainable method, which takes a user (or other Authenticatable object, depending on how your system is set up), as you can see in Example 12-10.
publicfunctiontest_guests_cant_view_dashboard(){$user=factory(User::class)->states('guest')->create();$response=$this->actingAs($user)->get('dashboard');$response->assertStatus(401);// Unauthorized}publicfunctiontest_members_can_view_dashboard(){$user=factory(User::class)->states('member')->create();$response=$this->actingAs($user)->get('dashboard');$response->assertOk();}publicfunctiontest_members_and_guests_cant_view_statistics(){$guest=factory(User::class)->states('guest')->create();$response=$this->actingAs($guest)->get('statistics');$response->assertStatus(401);// Unauthorized$member=factory(User::class)->states('member')->create();$response=$this->actingAs($member)->get('statistics');$response->assertStatus(401);// Unauthorized}publicfunctiontest_admins_can_view_statistics(){$user=factory(User::class)->states('admin')->create();$response=$this->actingAs($user)->get('statistics');$response->assertOk();}
It’s common to use model factories in testing (learn more in “Model Factories”), and model factory states make tasks like creating users with different access levels simple.
If you’d like to set session variables on your requests, you can also chain withSession():
$response=$this->withSession(['alert-dismissed'=>true,])->get('dashboard');
If you’d prefer to set your request headers fluently, you can chain withHeaders():
$response=$this->withHeaders(['X-THE-ANSWER'=>'42'])->get('the-restaurant-at-the-end-of-the-universe');
Usually, an exception that’s thrown inside your application when you’re making HTTP calls will be captured by Laravel’s exception handler and processed as it would be in normal application. So, the test and route in Example 12-11 would still pass, since the exception would never bubble up the whole way to our test.
// routes/web.phpRoute::get('has-exceptions',function(){thrownewException('Stop!');});// tests/Feature/ExceptionsTest.phppublicfunctiontest_exception_in_route(){$this->get('/has-exceptions');$this->assertTrue(true);}
In a lot of cases, this might make sense; maybe you’re expecting a validation exception and you want it to be caught like it would normally be by the framework.
But if you want to temporarily disable the exception handler, that’s an option; just run $this->withoutExceptionHandling():
// tests/Feature/ExceptionsTest.phppublicfunctiontest_exception_in_route(){// Now throws an error$this->withoutExceptionHandling();$this->get('/has-exceptions');$this->assertTrue(true);}
And if, for some reason, you need to turn it back on (maybe you turned it off in setUp() but want it back on for just one test) you can run $this->withExceptionHandling().
Often, the effect we want to test for after our tests have run in is in the database. Imagine you’re wanting to test that the “create package” page works correctly. What’s the best way? Make an HTTP call the “store package” endpoint and then assert that that package exists in the database. It’s easier and safer than inspecting the resulting “list packages” page.
We have two primary assertions for the database: $this->assertDatabaseHas() and $this->assertDatabaseMissing(). For both, pass the table name as the first parameter, the data you’re looking for as the second, and, optionally, the specific database connection you want to test as the third.
Take a look at Example 12-12 to see how you might use them.
publicfunctiontest_create_package_page_stores_package(){$this->post(route('packages.store'),['name'=>'Package-a-tron',]);$this->assertDatabaseHas('packages',['name'=>'Package-a-tron']);}
As you can see, the second “data” parameter of assertDatabaseHas() is structured like a SQL WHERE statement—you pass a key and a value (or multiple keys and values), and then Laravel looks for any records in the specified database table that match your key(s) and value(s).
As always, assertDatabaseMissing() is the inverse.
Model factories are amazing tools that make it easy to seed randomized, well-structured database data for testing (or other purposes). You’ve already seen them in use in several examples in this chapter.
We’ve already covered them in depth, so check out “Model Factories” to learn more.
If you use seeds in your application, you can run the equivalent of php artisan db:seed by running $this->seed() in your test.
You can also pass a seeder class name to just seed that one class:
$this->seed();// Seeds all$this->seed(UserSeeder::class);// Seeds users
We’ll cover what fakes are in “Mocking”, but for now just know that there are a good number of Laravel systems that allow you to pause their true function for a test and instead write tests against what has happened to those systems.
Imagine you have an event that’s being dispatched (“user signed up”) and it has a listener that notifies a Slack channel that a user signed up. First, you don’t want those notifications to go to Slack every time you run your tests. But second, you might want to assert that the event was sent, or the listener was triggered, or something else. This, and much more, is why we’ll want to fake many aspects of Laravel in our tests; to pause the default behavior and instead make assertions against those systems.
Let’s use event fakes as our first example of how Laravel makes it possible to mock its internal systems.
First, there are likely going to be times where you want to fake your events just for the sake of suppressing their actions. For example, if you have your app pushing notifications to Slack every time a new user signed up, and that’s happening in an event listener, you might want all of your events to stop working in your tests.
Let’s take a look at how to suppress these events by calling the fake() method on use Illuminate\Support\Facades\Event in Example 12-13.
publicfunctiontest_controller_does_some_thing(){Event::fake();// Call controller and assert it does whatever you want without// worrying about it pinging Slack}
But once we’ve run the fake() method, we can also call special assertions on the Event facade; namely, assertDispatched() and assertNotDispatched(). Take a look at Example 12-14 to see them in use.
publicfunctiontest_signing_up_users_notifies_slack(){Event::fake();// Sign user up...Event::assertDispatched(UserJoined::class,function($event)use($user){return$event->user->id===$user->id;});// Or sign multiple users up and assert it was dispatched twice...Event::assertDispatched(UserJoined::class,2);// Or sign up with validation failures and assert it wasn't dispatched...Event::assertNotDispatched(UserJoined::class);}
Note that the (optional) closure we’re passing to assertDispatched() makes it so we’re not just asserting that the event was dispatched, but we’re also asserting that the dispatched event contains certain data.
Event::fake() also disables Eloquent model events. So if you have any important code, for example, in a models’ creating event, make sure to create your models (through your factories or however else) before calling Event::fake().
Each of the following features in Laravel have their own set of assertions you can make after faking them, but you can also just choose to fake them to restrict their effects.
The bus facade, which represents how Laravel dispatches jobs, works just like events. You can run fake() on it to disable the impact of your jobs, and after faking it you can run assertDispatched() or assertNotDispatched().
The queue facade also represents how Laravel dispatches jobs, but for when they’re pushed up to queues. Its available methods are assertedPushed(), assertPushedOn(), and assertNotPushed().
Take a look at Example 12-15 to see how to use both.
publicfunctiontest_popularity_is_calculated(){Bus::fake();// Synchronize package data...// Assert a job was dispatchedBus::assertDispatched(CalculatePopularity::class,function($job)use($package){return$job->package->id===$package->id;});// Assert a job was not dispatchedBus::assertNotDispatched(DestroyPopularityMaybe::class);}publicfunctiontest_popularity_calculation_is_queued(){Queue::fake();// Synchronize package data...// Assert a job was pushed to any queueQueue::assertPushed(CalculatePopularity::class,function($job)use($package){return$job->package->id===$package->id;});// Assert a job was pushed to a given queue named "popularity"Queue::assertPushedOn('popularity',CalculatePopularity::class);// Assert a job was pushed twice...Queue::assertPushed(CalculatePopularity::class,2);// Assert a job was not pushed...Queue::assertNotPushed(DestroyPopularityMaybe::class);}
The mail facade, when faked, offers four methods: assertSent(), assertNotSent(), assertQueued(), and assertNotQueued(). Use the Queued methods when your mail is queued and the Sent methods when it’s not.
Just like with assertDispatched, the first parameter will be the name of the mailable and the second parameter can be empty, the number of times the mailable has been sent, or a closure testing that the mailable has the right data in it. Take a look at Example 12-16 to see it in action.
publicfunctiontest_package_authors_receive_launch_emails(){::fake();// Make a package public for the first time...// Assert a message was sent to a given email address::assertSent(PackageLaunched::class,function()use($package){return->package->id===$package->id;});// Assert a message was sent to given email addresses::assertSent(PackageLaunched::class,function()use($package){return->hasTo($package->author->)&&->hasCc($package->collaborators)&&->hasBcc('admin@novapackages.com');});// Or, launch two packages...// Assert a mailable was sent twice::assertSent(PackageLaunched::class,2);// Assert a mailable was not sent...::assertNotSent(PackageLaunchFailed::class);}
All of the messages checking for recipients (hasTo(), hasCc(), and hasBcc()) can take either a single email address or an array or collection of addresses.
The notification facade, when faked, offers two methods: assertSentTo() and assertNothingSent().
Unlike the mail facade, you’re not going to test who it was sent to manually in a closure. Rather, the assertion itself requires the first parameter be either a single notifiable object or an array or collection of them. Only after you’ve passed in the desired notification target can you test anything about the notification itself.
The second parameter is the class name for the notification, and the (optional) third parameter can be a closure defining more expectations about the notification. Take a look at Example 12-17 to learn more.
publicfunctiontest_users_are_notified_of_new_package_ratings(){Notification::fake();// Perform package rating...// Assert author was notifiedNotification::assertSentTo($package->author,PackageRatingReceived::class,function($notification,$channels)use($package){return$notification->package->id===$package->id;});// Assert a notification was sent to the given usersNotification::assertSentTo([$package->collaborators],PackageRatingReceived::class);// Or, perform a duplicate package rating...// Assert a notification was not sentNotification::assertNotSentTo([$package->author],PackageRatingReceived::class);}
You may also find yourself wanting to assert that your channel selection is working—that notifications are sent via the right channels. You can test that as well, as you can see in Example 12-18.
publicfunctiontest_users_are_notified_by_their_preferred_channel(){Notification::fake();$user=factory(User::class)->create(['slack_preferred'=>true]);// Perform package rating...// Assert author was notified via SlackNotification::assertSentTo($user,PackageRatingReceived::class,function($notification,$channels)use($package){return$notification->package->id===$package->id&&in_array('slack',$channels);});
Testing files can be extraordinarily complex. Many traditional methods require you to actually move files around in your test directories, and formatting the form input and output can be very complicated.
Thankfully, if you use Laravel’s Storage facade, it’s endlessly simpler to test file uploads and other storage-related items.
publicfunctiontest_package_screenshot_upload(){Storage::fake('screenshots');// Upload a fake image$response=$this->postJson('screenshots',['screenshot'=>UploadedFile::fake()->image('screenshot.jpg')]);// Assert the file was storedStorage::disk('screenshots')->assertExists('screenshot.jpg');// Or, assert a file does not existStorage::disk('screenshots')->assertMissing('missing.jpg');}
Mocks (and their brethren, spies and stubs and dummies and fakes and any number of other tools) are common tools in testing. We’ve taken a look at fakes a bit here, but mainly from a surface level. I won’t go into great detail here, but it’s unlikely you can thoroughly test an application of any size without mocking at least one thing or another.
So here we will take a quick look at mocking in Laravel and how to use Mockery, a mocking library included in Laravel.
Essentially, mocks and other similar tools make it possible to create an object that in some way mimics a real class, but for testing purposes isn’t the real class. Sometimes this is done because the real class is too difficult to instantiate just to inject it into a test, or maybe the real class communicates with an external service.
As you can probably tell from the examples that follow, Laravel encourages working with the real application as much as possible—which means avoiding too great of a dependence on mocks. But they have their place, which is why Laravel includes Mockery, a mocking library, out of the box, and is why many of its core services offer faking utilities.
Mockery allows you to quickly and easily create mocks from any PHP class in your application. Imagine you have a class that depends on a Slack client, but you don’t want the calls to actually go out to Slack. Mockery makes it simple to create a fake Slack client to use in your tests, like you can see in Example 12-20.
// app/SlackClient.phpclassSlackClient{// ...publicfunctionsend($message,$channel){// Actually sends a message to Slack}}// app/Notifier.phpclassNotifier{private$slack;publicfunction__construct(SlackClient$slack){$this->slack=$slack;}publicfunctionnotifyAdmins($message){$this->slack->send($message,'admins');}}// tests/Unit/NotifierTest.phppublicfunctiontest_notifier_notifies_admins(){$slackMock=Mockery::mock(SlackClient::class)->shouldIgnoreMissing();$notifier=newNotifier($slackMock);$notifier->notifyAdmins('Test message');}
There are a lot of elements at work here, but if you look at them one by one, they make sense. We have a class named Notifier that we’re testing. It has a dependency named SlackClient that does something that we don’t want it to do when we’re running our tests: it sends actual Slack notifications. So we’re going to mock it.
We use Mockery to get a mock of our SlackClient class. If we don’t care about what happens to that class—if it should simply exist to keep our tests from throwing errors—we can just use shouldIgnoreMissing():
$slackMock=Mockery::mock(SlackClient::class)-shouldIgnoreMissing();
No matter what Notifier calls on $slackMock, it’ll just accept it and return null.
But take a look at test_notifier_notifies_admins(). At this point, it doesn’t actually test anything.
We could just keep shouldIgnoreMissing() and then write some assertions below it. That’s usually what we do with shouldIgnoreMissing(), which makes this object a “fake” or a “stub.”
But what if we want to actually assert that a call was made to the send() method of SlackClient? That’s when we drop shouldIgnoreMissing() and reach for the other should* methods (Example 12-21).
publicfunctiontest_notifier_notifies_admins(){$slackMock=Mockery::mock(SlackClient::class);$slackMock->shouldReceive('send')->once();$notifier=newNotifier($slackMock);$notifier->notifyAdmins('Test message');}
shouldReceive('send')->once() is the same as saying “assert that $slackMock will have its send() method called once and only once.” So, we’re now asserting that Notifier, when we call notifyAdmins(), must make a single call to the send method on SlackClient.
We could also use something like shouldReceive('send')->times(3) or shouldReceive('send')->never(). We can define what parameter we expect to be passed along with that send() call using with(), and we can define what to return with andReturn():
$slackMock->shouldReceive('send')->with('Hello, world!')->andReturn(true);
What if we wanted to use the IoC container to resolve our instance of the Notifier? This might be useful if Notifier had several other dependencies that we didn’t need to mock.
We can do that! We just use the instance() method on the container, as in Example 12-22, to tell Laravel to provide an instance of our mock to any classes that request it (which, in this example, will be Notifier).
publicfunctiontest_notifier_notifies_admins(){$slackMock=Mockery::mock(SlackClient::class);$slackMock->shouldReceive('send')->once();app()->instance(SlackClient::class,$slackMock);$notifier=app(Notifier::class);$notifier->notifyAdmins('Test message');}
There’s a lot more you can do with Mockery: you can use spies, and partial spies, and much more. Going deeper into how to use Mockery is out of the scope of this book, but I encourage you to learn more about the library and how it works at the Mockery docs.
There’s one other clever thing you can do with Mockery: you can use Mockery methods (e.g. shouldReceive()) on any facades in your app.
Imagine we have a controller method that uses a facade that’s not one of the fake-able systems we’ve already covered; we want to test that controller method and assert that a certain facade call was made.
Thankfully, it’s simple: we can run our Mockery-style methods on the facade, as you can see in Example 12-23.
// PeopleControllerpublicfunctionindex(){returnCache::remember('people',function(){returnPerson::all();});}// PeopleTestpublicfunctiontest_all_people_route_should_be_cached(){$person=factory(Person::class)->create();Cache::shouldReceive('remember')->once()->andReturn(collect([$person]));$this->get('people')->assertJsonFragment(['name'=>$person->name]);}
As you can see, you can use methods like shouldReceive() on the facades, just like you do on a Mockery object.
You can also use your facades as spies, which means you can set your assertions at the end and use shouldHaveReceived() instead of shouldReceive(). Example 12-24 illustrates this.
publicfunctiontest_package_should_be_cached_after_visit(){Cache::spy();$package=factory(Package::class)->create();$this->get(route('packages.show',[$package->id]));Cache::shouldHaveReceived('put')->once()->with('packages.'.$package->id,$package->toArray());}
We’ve covered a lot in this chapter, but we’re almost done! We have just two more pieces of Laravel’s testing arsenal to cover: Artisan and the browser.
If you’re working in Laravel prior to 5.7, the best way to test Artisan commands is to call them with $this->artisan($commandName, $parameters) and then test their impact, like in Example 12-25.
publicfunctiontest_promote_console_command_promotes_user(){$user=factory(User::class)->create();$this->artisan('user:promote',['userId'=>$user->id]);$this->assertTrue($user->isPromoted());}
You can also make assertions against the response code you are getting from Artisan, as you can see in Example 12-26.
$code=$this->artisan('do:thing',['--flagOfSomeSort'=>true]);$this->assertEquals(0,$code);// 0 means "no errors were returned"
If you’re working with Laravel 5.7 and later, you can also chain on three new methods to your $this->artisan() call: expectsQuestion(), expectsOutput(), and assertExitCode(). The expects methods will work on any of the interactive prompts, including confirm(), anticipate(), and more, and the assertExitCode() method is a shortcut to what we looked at in Example 12-26.
Take a look at Example 12-27 to see how it works.
// routes/console.phpArtisan::command('make:post {--expanded}',function(){$title=$this->ask('What is the post title?');$this->comment('Creating at '.str_slug($title).'.md');$category=$this->choice('What category?',['technology','construction'],0);// Create post here$this->comment('Post created');});
// Test filepublicfunctiontest_make_post_console_commands_performs_as_expected(){$this->artisan('make:post',['--expanded'=>true])->expectsQuestion('What is the post title?','My Best Post Now')->expectsOutput('Creating at my-best-post-now.md')->expectsQuestion('What category?','construction')->expectsOutput('Post created')->assertExitCode(0);}
As you can see, the first parameter of expectsQuestion() is the text we’re expecting to see from the question, and the second parameter is the text we’re answering with. expectsOutput() just tests that the passed string is returned.
We’ve made it to browser tests! With browser tests, you can actually interact with the DOM of your pages: click buttons, fill out and submit forms, and, with Dusk, even interact with JavaScript.
Laravel actually has two separate browser testing tools: BrowserKit Testing and Dusk. Dusk is actively maintained; BrowserKit Testing seems to have become a bit of a second class citizen, but it’s still available on GitHub and still works at the time of this writing.
When choosing a tool for browser testing, I suggest you use the core application testing tools whenever possible (those which we’ve covered up until this point). If your app is not JavaScript-based and you need to test actual DOM manipulation or form UI elements, use BrowserKit. If you’re developing a JavaScript-heavy app, you’ll likely want to use Dusk, which we’ll cover next.
However, there are many instances where you’ll want to use a JavaScript-based test stack (which is out of scope for this book) based on something like Jest and vue-test-utils. This toolset can be very useful for Vue component testing and Jest’s snapshot functionality simplifies the process of keeping API and frontend test data in sync. To learn more, check out this blog post from Caleb Porzio and this Laracon talk from Samantha Geitz.
If you’re working with a JavaScript framework other than Vue, there are no currently preferred frontend testing solutions in the Laravel world. However, the broad React world seems to have settled on Jest and Enzyme.
Dusk is a Laravel tool (installable as a Composer package) which makes it easy to write Selenium-style directions for a ChromeDriver-based browser to interact with your app. Unlike most other Selenium-based tools, Dusk’s API is simple and easy to write by hand. Take a look:
$this->browse(function($browser){$browser->visit('/register')->type('email','test@example.com')->type('password','secret')->press('Sign Up')->assertPathIs('/dashboard');});
With Dusk, there’s an actual browser spinning up your entire application and interacting with it. That means you can have complex interactions with your JavaScript and get screenshots of failure states; but it also means everything’s a bit slower and more prone to failure than Laravel’s base application testing suite.
Personally, I’ve found the greatest use in Dusk as a regression testing suite, to replace something like Selenium. Rather than using it for any sort of test-driven development, I’d use it to assert that the user experience hasn’t broken (“regressed”) as the app continues to develop. Think of this more like writing tests about your user interface after the interface is built.
The Dusk docs are robust, so I’m not going to go into great depth here, but I want to show you the basics of working with Dusk.
To install Dusk, run these two commands:
composer require --dev laravel/dusk php artisan dusk:install
Then edit your .env file to set your APP_URL variable to the same URL you use to view your site in your local browser; something like http://mysite.test.
To run your Dusk tests, just run php artisan dusk, and you can pass all the same parameters you’re used to from PHPUnit (e.g. php artisan dusk --filter=my_best_test).
To generate a new Dusk test, use the following command:
php artisan dusk:make RatingTest
This test will be placed in tests/Browser/RatingTest.php.
You can customize the environment variables for Dusk by creating a new file named .env.dusk.local (and you can replace .local if you’re working in a different environment, like “staging”).
To write your Dusk Tests, imagine that you’re directing one or more web browsers to go visit your application and take certain actions. That’s what the syntax will look like, as you can see in Example 12-28.
publicfunctiontestBasicExample(){$user=factory(User::class)->create();$this->browse(function($browser)use($user){$browser->visit('login')->type('email',$user->)->type('password','secret')->press('Login')->assertPathIs('/home');});}
$this->browse() creates a browser, which you pass into a closure, and then within the closure you instruct the browser which actions to take.
It’s important to note that, unlike Laravel’s other application testing tools, which mimic the behavior of your forms, Dusk is actually spinning up a browser, sending events to the browser to type those words, and then sending an event to the browser to press that button. This is a real browser and Dusk is fully driving it.
You can also “ask” for more than one browser by adding parameters to the closure, which allows you to test how multiple users might interact with the web site (for example, with a chat system). Take a look at this example from the docs:
$this->browse(function($first,$second){$first->loginAs(User::find(1))->visit('home')->waitForText('Message');$second->loginAs(User::find(2))->visit('home')->waitForText('Message')->type('message','Hey Taylor')->press('Send');$first->waitForText('Hey Taylor')->assertSee('Jeffrey Way');});
There’s a huge suite of actions and assertions available that we won’t cover here (check the docs), but let’s look at a few of the other tools Dusk provides.
As you can see in Example 12-29, the syntax for authentication is a little different from the rest of the Laravel application testing: $browser->loginAs($user).
Don’t use the RefreshDatabase trait with Dusk! Use the DatabaseMigrations trait; transactions, which RefreshDatabase uses, don’t last across requests.
If you’ve ever written jQuery, interacting with the page using Dusk will come naturally. Take a look at Example 12-30 to see the common patterns for selecting items with Dusk.
// Template<divclass="search"><input><buttonid="search-button"></button></div><buttondusk="expand-nav"></button>
// Dusk tests// Option 1: jQuery-style syntax$browser->click('.search button');$browser->click('#search-button');// Option 2: dusk="selector-here" syntax; recommended$browser->click('@expand-nav');
As you can see, adding the dusk attribute to your page elements allows you to reference them directly in a way that won’t change when the display or layout of the page changes later; when any method asks for a selector, pass in the @ sign and then the content of your dusk attribute.
Let’s take a look at a few of the methods you can call on $browser.
value($selector, $value = null)Returns the value of any text input if only one parameter is passed; sets the value of an input if a second parameter was passed.
text($selector)Gets the text content of a non-fillable item like a div or a span.
attribute($selector, $attributeName)Returns the value of a particular attribute on the element matching $selector.
type($selector, $valueToType)Similar to value(), but actually types the characters rather than directly setting the value.
With methods like type() that target inputs, Dusk will start by trying to match a Dusk or CSS selector, and then will try to look for an input with the provided name, and finally will try to find a textarea with the provided name.
select($selector, $optionValue)Selects the option with the value of $optionValue in a dropdown selectable by $selector.
check($selector) and uncheck($selector)Checks or unchecks a checkbox selectable by $selector.
radio($selector, $optionValue)Selects the option with the value of $optionValue in a radio group selectable by $selector.
attach($selector, $filePath)Attaches a file at $filePath to the file input selectable by $selector.
clickLink($selector)Follows a text link to its target.
click($selector) and mouseover($selector)Triggers a mouse click or a mouseover event on $selector.
drag($selectorToDrag, $selectorToDragTo)Drags an item to another item.
dragLeft(), dragRight(), dragUp(), and dragDown()Given a first parameter of a selector and a second parameter of a number of pixels, drags the selected item that many pixels in the given direction.
keys($selector, $instructions…)Sends keypress events within the context of $selector according to the instructions in $instructions. Take a look at Example 12-31 to see how to combine modifiers with your typing.
$browser->keys('selector','this is ',['{shift}','great']);
This would type “this is GREAT”; as you can tell, adding an array to the list of items to type allows you to combine modifiers (wrapped with {}) with typing. You can see a full list of the possible modifiers at the Facebook WebDriver source.
If you’d like to just send your key sequence to the page (for example, to trigger a “keyboard shortcut”), you can just target the top level of your app or page as your selector. For example, if it’s a Vue app and the top level is a div with an ID of app:
$browser->keys('#app',['{command}','/']);
Because Dusk interacts with JavaScript and is directing an actual browser, the concept of time and timeouts and “waiting” needs to be addressed. Dusk offers a few methods you can use to ensure your tests handle timing issues correctly. Some of these methods are useful for interacting with intentionally slow or delayed elements of the page, but some of them are also just useful for getting around initialization times on your components.
pause($milliseconds)Pauses the execution of Dusk tests for the given number of milliseconds. The simplest “wait” option; makes any future commands you send to the browser wait that amount of time before operating.
Take a look at Example 12-32 to see how to use this and other waiting methods in the midst of an assertion chain.
$browser->click('chat')->pause(500)->assertSee('How can we help?');
waitFor($selector, $maxSeconds = null) and waitForMissing($selector, $maxSeconds = null)Waits until the given element exists on the page (waitFor()) or disappears from the page (waitForMissing()), or times out after the optional second parameter’s second count.
$browser->waitFor('@chat',5);$browser->waitUntilMissing('@loading',5);
whenAvailable($selector, $callback)Similar to waitFor(), but accepts a closure as the second parameter which will define what action to take when the specified element becomes available.
$browser->whenAvailable('@chat',function($chat){$chat->assertSee('How can we help you?');});
waitForText($text, $maxSeconds = null)Waits for text to show up on the page, or times out after the optional second parameter’s second count.
$browser->waitForText('Your purchase has been completed.',5);
waitForLink($linkText, $maxSeconds = null)Waits for a link to exist with the given link text, or times out after the optional second parameter’s second count.
$browser->waitForLink('Clear these results',2);
waitForLocation($path)Waits until the page URL matches the provided path.
$browser->waitForLocation('auth/login');
waitForRoute($routeName)Waits until the page URL matches the URL for the provided route.
$browser->waitForRoute('packages.show',[$package->id]);
waitForReload()Waits until the page reloads.
waitUntil($expression)Waits until the provided JavaScript expression evaluates as true:
$browser->waitUntil('App.packages.length > 0',7);
As I’ve mentioned, there’s a huge list of assertions you can make against your app with Dusk. Here are a few that I use most commonly, and you can see the full list in the Dusk docs.
assertTitleContains($text)
assertQueryStringHas($keyName)
assertHasCookie($cookieName)
assertSourceHas($htmlSourceCode)
assertChecked($selector)
assertSelectHasOption($selectorForSelect, $optionValue)
assertVisible($selector)
assertFocused()
assertVue($dataLocation, $dataValue, $selector)
So far, everything we’ve covered makes it possible for you to test individual elements on your pages. But Dusk will often be used to test more complex applications and single-page apps, which means we’re going to need organizational structures around our assertions.
The first organizational structures we have encountered have been the Dusk attribute (<div dusk="abc"> creating a selector named @abc you can refer to later) and the closures we can use towrap certain portions of our code (for example, with whenAvailable()).
Dusk offers two more organizational tools: Pages and Components. Let’s start with pages.
A page is a class that you’ll generate which contains two pieces of functionality: first, a URL and assertions to define which page in your app should be attached to this Dusk page, and second, shorthand like we used inline (@abc generated by the dusk="abc" attribute in our HTML) but just for this page, and without needing to edit our HTML.
To generate your first Dusk page, let’s imagine we have a “create package” page of our app. Generate a Dusk page for it:
phpartisandusk:pageCreatePackage
Take a look at Example 12-33 to see what our generated class will look like.
<?phpnamespaceTests\Browser\Pages;useLaravel\Dusk\Browser;classCreatePackageextendsPage{/*** Get the URL for the page.** @return string*/publicfunctionurl(){return'/';}/*** Assert that the browser is on the page.** @param Browser $browser* @return void*/publicfunctionassert(Browser$browser){$browser->assertPathIs($this->url());}/*** Get the element shortcuts for the page.** @return array*/publicfunctionelements(){return['@element'=>'#selector',];}}
The url() method defines where Dusk should expect the location to be for this page; assert() lets you run additional assertions to verify you’re on the right page; and elements() provides shortcuts for @dusk-style selectors.
Let’s make a few quick modifications for our “create package” page, to make it look like Example 12-34.
classCreatePackageextendsPage{publicfunctionurl(){return'/packages/create';}publicfunctionassert(Browser$browser){$browser->assertTitleContains('Create Package');$browser->assertPathIs($this->url());}publicfunctionelements(){return['@title'=>'input[name=title]','@instructions'=>'textarea[name=instructions]',];}}
Now that we have a functional page, we can navigate to it, and access its defined elements:
// In a test$browser->visit(newTests\Browser\Pages\CreatePackage)->type('@title','My package title');
One common use for pages is to define a common action you want to take in your tests; consider these almost like macros for Dusk. You can define a method on your page and then call it from your code, as you can see in Example 12-35.
classCreatePackageextendsPage{// ... url(), assert(), elements()publicfunctionfillBasicFields(Browser$browser,$packageTitle='Best package'){$browser->type('@title',$packageTitle)->type('@instructions','Do this stuff and then that stuff');}}
$browser->visit(newCreatePackage)->fillBasicFields('Greatest Package Ever')->press('Create Package')->assertSee('Greatest Package Ever');
If you want the same functionality as Dusk pages offer, but without the constraint to a specific URL, you’ll likely want to reach for Dusk components. These classes are shaped very similarly to pages, but instead of being bound to a URL, they’re each bound to a selector.
In NovaPackages.com, we have a little Vue component for rating packages and displaying ratings. Let’s make a Dusk component for it.
php artisan dusk:component RatingWidget
Take a look at Example 12-36 to see what that will generate.
<?phpnamespaceTests\Browser\Components;useLaravel\Dusk\Browser;useLaravel\Dusk\ComponentasBaseComponent;classRatingWidgetextendsBaseComponent{/*** Get the root selector for the component.** @return string*/publicfunctionselector(){return'#selector';}/*** Assert that the browser page contains the component.** @param Browser $browser* @return void*/publicfunctionassert(Browser$browser){$browser->assertVisible($this->selector());}/*** Get the element shortcuts for the component.** @return array*/publicfunctionelements(){return['@element'=>'#selector',];}}
As you can see, this is basically the same as a Dusk page, but we’re encapsulating our work to an HTML element instead of a URL. Everything else is basically the same. Take a look at Example 12-37 to see our rating widget example in Dusk component form.
classRatingWidgetextendsBaseComponent{publicfunctionselector(){return'.rating-widget';}publicfunctionassert(Browser$browser){$browser->assertVisible($this->selector());}publicfunctionelements(){return['@5-star'=>'.five-star-rating','@4-star'=>'.four-star-rating','@3-star'=>'.three-star-rating','@2-star'=>'.two-star-rating','@1-star'=>'.one-star-rating','@average'=>'.average-rating','@mine'=>'.current-user-rating',];}publicfunctionratePackage(Browser$browser,$rating){$browser->click("@{$rating}-star")->assertSeeIn('@mine',$rating);}}
Using components works just like using pages, as you can see in Example 12-38.
$browser->visit('/packages/tightenco/nova-stock-picker')->within(newRatingWidget,function($browser){$browser->ratePackage(2);$browser->assertSeeIn('@average',2);});
That’s a good, brief overview of what Dusk can do. There’s a lot more—more assertions, more edge cases, more gotcha’s, more examples—in the Dusk docs, so I’d recommend a read through there if you plan to work with Dusk.
Laravel can work with any modern PHP testing framework, but it’s optimized for PHPUnit, especially if your tests extend Laravel’s TestCase. Laravel’s application testing framework makes it simple to send fake HTTP and console requests through your application and inspect the results.
Tests in Laravel can easily and powerfully interact and assert against the database, cache, session, filesystem, mail, and much more. Quite a few of these systems have fakes built-in to make them even easier to test. You can test DOM and browser-like interactions with BrowserKit Testing or Dusk.
Laravel brings in Mockery in case you need mocks, stubs, spies, dummies, or anything else, but the testing philosophy of Laravel is to use real collaborators as much as possible. Don’t fake it unless you have to.