Reviewing the Pest tests0:00
Take a look at the following test file, which is basically a pestified version of the contacts test that ships with pingcrm in phpunit. If I look at the phpstorm test overview you'll see we have 4 tests created. We can view contacts, we can search for contacts, we cannot view deleted contacts by default, but if we filter we can indeed view deleted contacts. Ok, let's take a look at the logic for our first test here. Because the test closure has access to the underlying test case, we can actually call $this, and this refers to the phpunit test case that is backing up this test in particular, and then we can call actingAs method, which is from Laravel, and it allows us to log a user in. We then get the contacts endpoint, and we're going to use this handy assertInertia macro that ships with InertiaJS to make sure that the index.vue component was loaded, that it has
Running tests and diagnosing failures0:56
get the contacts endpoint, and we're going to use this handy assertInertia macro that ships with Inertia to make sure that the index.vue component was loaded, that it has two contacts passed to it, and that the first contact is martinabbott, and the second contact is lynncub. Now if we jump into our terminal and I run php artisan test, I'm going to pass a filter to only run the passContacts test file. If I execute this I'm actually going to get 4 failing tests, and each test has the same error, undefined property user. Now if we jump back into our IDE and we take a look at the top of this test, you'll see that we did indeed log in as a User which is supposed to be set on the test itself, but we obviously never created that User anywhere in this test. Let's see how the phpunit version of this test is handled. You'll note that they actually have a setUp method which is part of phpunit's
Using Pest lifecycle hooks1:45
never created that User anywhere in this test. Let's see how the phpunit version of this test is handled. You'll note that they actually have a setup method which is part of phpunit's API and runs before each test in a class is executed, and you'll see in that setup they actually assign a user property to the test class, and they create a new User model using class based model factories. They also assign two contacts to that user, the first of which is martinabbott, and the second of which is lynncub. So we need to translate this code into pest.php, and we do that by making use of pest's testing hooks. There are 4 different testing hooks that you can make use of. beforeAll, let's vardump in here beforeAll just so we can see it in action. We have beforeEach, let's vardump beforeEach. We have afterEach, again we'll vardump afterEach, and we have afterAll, and as you'd expect we'll vardump
can see it in action. We have beforeEach, let's vardump beforeEach. We have afterEach, again we'll vardump afterEach, and we have afterAll, and as you'd expect we'll vardump afterAll. Now obviously the tests are still going to fail, but if I run this again and scroll up to the top, you're going to see how this is ordered. Before all of those 4 tests are executed, the beforeAll hook is run. Then for each test I have a beforeEach, followed by an afterEach, a beforeEach, followed by an afterEach, and this is in order of the tests as they're declared in the test file. And finally, once all of the tests have been executed, the afterAll hook is executed a single time. So you can make use of these lifecycle hooks in order to perform certain setup or tear down logic in your test. Let's jump back into the IDE because I just want to show you something.
Choosing hooks with Laravel3:32
So you can make use of these lifecycle hooks in order to perform certain setup or tear down logic in your test. Let's jump back into the IDE because I just want to show you something that's important when deciding which hook to use. The beforeAll and afterAll hooks do not have access to any Laravel features. That's because Laravel hasn't been booted at the time of these hooks being executed. So if I jump into here for example, and let's say I want to vardump config.app.name. If I jump into my terminal and attempt to execute that test, you'll see that it fails because targetClassConfig does not exist. In other words, the config facade has not been bound in Laravel. However, if I jump back into my IDE and I'll revert that change, and I'll vardump the app.name in here instead, in the beforeEach hook, and I jump into my terminal and run the tests.
However, if I jump back into my IDE and I'll revert that change, and I'll vardump the app.name in here instead, in the beforeEach hook, and I jump into my terminal and run the tests again, you'll see that when we look at the output up here, it actually outputs the app.name, in this case Laravel, in each test. So you have access to Laravel in the beforeEach and afterEach hooks, but you do not have access to it in the beforeAll and afterAll hooks. So the beforeEach hook seems like the correct hook for us to use in this case. I'll get rid of the others. And seeing as it's going to be a number of lines long, let's convert this to a long closure. So function, and then we'll open up the function body and I'll jump into this BaseTestCase here that I'm basing this entire Pest test on. We'll grab all of this logic here, and I'm simply going to paste it in the beforeEach hook, import the correct
this base test case here that I'm basing this entire Pest test on. We'll grab all of this logic here, and I'm simply going to paste it in the beforeEach hook, import the correct classes. And now let's run this again. You'll see that we have one passing test, which is better, but there are still three failing tests. Let's take a look at why. Note that there is now an integrity constraint violation that says that that email address already exists. So there's basically a unique constraint on an email address for a User. And for some reason, users aren't being cleared in our database between tests. So what's going on here? Well, if we jump back into our IDE and take a look at the PHPUnit version of this test, you'll note that they make use of the RefreshDatabase trait. This trait ships with Laravel, and it basically re-migrates the database between each and every test in the
Refreshing database in Pest5:54
test, you'll note that they make use of the RefreshDatabase trait. This trait ships with Laravel, and it basically re-migrates the database between each and every test in the file, which means you always start with a fresh slate to work on for your tests. Now, obviously we're not in a class in PestPHP. So how can we make use of this trait? Well, I'm going to show you two ways. The first is the direct translation of this. And the second is actually a way that I think is better than what ships default in PingCRM. So you can make use of the users function, which is available with Pest, and you pass the users function any traits you want to make use of in your test, in this case, RefreshDatabase. So we'll make use of that there, and I'll import it. And now when I run these tests again, I actually have four passing tests. Everything works as expected. And you can
Switching to LazyRefreshDatabase6:39
So we'll make use of that there, and I'll import it. And now when I run these tests again, I actually have four passing tests. Everything works as expected. And you can include as many different traits as you want in this User's call. They'll all be dynamically added to the test that Pest generates. However, it's annoying to have to include this refreshDatabase on every single feature test. So there is actually a better way to do this in Laravel. I'm going to remove that entirely, and I'll remove this import as well because we no longer need it. And I'm going to go to the BaseTestCase, which uses createApplication. This is the test case that boots Laravel in the first place and allows us to use Laravel features in our tests. I'm going to import a second trait, which is lazilyRefreshDatabase. Okay. And I'll need to obviously import that class from Laravel. So what's the difference
features in our tests. I'm going to import a second trait, which is RefreshDatabase. Okay. And I'll need to obviously import that class from Laravel. So what's the difference between RefreshDatabase and LazyRefreshDatabase? Well, LazyRefreshDatabase isn't going to refresh your database unless your test hits the database. So it will wait until the last possible moment to migrate your database, which means that if your test never relies on it, it doesn't take a performance hit because of that fact. So this is super useful if you want to place this on your BaseTestCase and then forget about it for all of the tests in your application. And if I run the tests again, you'll see we still have those four passing tests. So that's testing hooks. Remember beforeAll, beforeEach, afterEach and afterAll, as well as the users function, which you can make use of to perform setup,
those four passing tests. So that's testing hooks. Remember beforeAll, beforeEach, afterEach and afterAll, as well as the users function, which you can make use of to perform setup, tear down and include different traits in your tests.
