Improving Model Assertions0:00
Take a look at this test that I've created for ensuring a User can log in. We create some credentials, we then create a User with those credentials, and we make a POST request to the logging route, passing those credentials. We then check that the authenticated User is the given User. And of course that returns a boolean, which means we have to check that it is true. Now you will use this form of logic everywhere when comparing models in your tests. But it doesn't read as well as I personally think it could. I think it would read much better if we could use one of the existing expectations. Let me show you what I mean. Imagine if we could write expect(auth()->user())->toBe($user).
Let me show you what I mean. Imagine if we could write expect $authUser to be $user. Wouldn't that be the nicest way of writing this expectation? But as you can imagine, if I remove that and then use the PEST dirty flag again in order to just run this one test, it fails. Because toBe will check that two variables reference exactly the same object. Now in PEST v1 there's not really anything you could do about this. You would either have to continue to use the toBeTrue way of doing things, or you'd have to extend the expectation API and create something like toBeModel. There was no way to intercept an existing expectation and use it for your own purposes.
Intercepting toBe for Models1:22
to extend the expectation API and create something like 2b model. There was no way to intercept an existing expectation and use it for your own purposes. But that's changed in PEST v2. So in this episode why don't we work to make this test pass. Let's intercept the 2b call and add our own variation of it for checking that one model is the same as another model. It all starts in the PEST.php file where we do our PEST configuration and I'm going to place it inside the EXTEND area where we've already created this 2b phone number extension. What I'll do is I'll say EXPECT and close it off as usual as if I were extending the expectation API.
What I'll do is I'll say EXPECT and close it off as usual as if I were extending the expectation API. But instead of calling EXTEND I'm going to call INTERCEPT. Now what do I want to intercept? Well I want to intercept the 2b expectation. Next we have to pass what's known as a filter. What is a filter? Well take a look at our expectation again. We pass in an instance of a User model. Now what is the circumstance under which we want our intercept to take hold?
We pass in an instance of a User model. Now what is the circumstance under which we want our intercept to take hold? Basically it's whenever the expectation itself is a model. If it's a model that we've passed in then we want our intercepted version of this call to take hold to happen to be executed. So in our case the filter is going to be the underlying model class. In other words, look PEST, any time I give you a model I want you to use this version of 2b instead of the standard version of 2b. The third parameter we need to pass to intercept is a closure. What goes inside the closure?
The third parameter we need to pass to intercept is a closure. What goes inside the closure? Well this is where the logic of our intercepted expectation takes place. This will be executed whenever the value that's been passed to expect is a model and we call toBe on the expectation API. It receives any parameters that would usually be passed to the toBe method. So in this case we expect it to be another model instance. That being the case we're able to say expect this value, which is the original model that was passed into expect, is and then this is the value that's been passed into the toBe call to be true, which is more or less duplicating the original functionality we
was passed into expect, is and then this is the value that's been passed into the 2b call to be true, which is more or less duplicating the original functionality we had but now wrapped up in this gorgeous 2b call which allows us to write the test as we originally wanted to create it. Let's see if this works by running pest dirty again. You can see everything passes. Just to be sure that this is actually working, why don't we head back into our login test and instead of passing this user, let's create another user by newing a factory up and calling create. If we run pest dirty again, you'll see everything fails.
Customizing Failure Messages4:26
create. If we run pest dirty again, you'll see everything fails. It's going to say failed asserting that false is true. That's not a great error message. It could be much more descriptive and here's another thing that's been vastly improved in PEST v2. You can now pass a message property to any expectation call to customise the error message that is shown in the output. So here we could say failed asserting that two models are the same and when we run the tests again, you'll see that the error output is changed to failed asserting that the two
other circumstance by the way. If I say expect foo to be bar and I rerun the test, it's going to fail and it's going to say failed asserting that two strings are identical. The reason it does that is because, as we said before, the filter only checks for an instance of a model here. So because we've passed a string, it's going to use the standard implementation of toBe. So you don't have to worry about your existing test breaking. You can intercept and extend existing expectations only when it makes sense to your particular use case. Can you think of another scenario where intercepting existing expectations will make our tests
Simplifying Inertia Assertions6:05
use case. Can you think of another scenario where intercepting existing expectations will make our tests easier to read and write? Well I think there is another area and that's inside the context test. If you take a look at this can view context test, we use this assertInertiaHas method over and over again. And inside that has method, we check that a certain property exists in the returned inertia JSON and that it has a specified value, either a literal value or using a closure. Wouldn't it be nice if there was an easier way to write these checks? Well I think there is an expectation that would be beautiful in this scenario.
Intercepting toContain for Responses6:37
Wouldn't it be nice if there was an easier way to write these checks? Well I think there is an expectation that would be beautiful in this scenario. What if you could take a response and say expect response to contain and then you say in this case contacts.data followed by the value you want to check to in this case here. Can we write this using interceptors? Well of course we can. It's very, very simple. Let's go back into our pest.php configuration file and let's intercept the toContain method. So expect intercept to contain. The filter is going to be testResponse which is the class that is returned whenever you
So expect intercept to contain. The filter is going to be testResponse which is the class that is returned whenever you call the get method or the put or post method inside a test. Our third parameter is the closure where our actual value will rest and I'm just going to intercept an array of values in there so that whatever is passed in I will forward to the assertInertia check. So knowing that this value is going to be a testResponse I can say assertInertia which is going to accept a closure which receives an assertableInertia object. So there's inertia and inside there I can say inertia->has and I'm just going to pass in the values like so.
So there's Inertia and inside there I can say Inertia has and I'm just going to pass in the values like so. And that is the full check. That will allow me hopefully to create a contacts test where I first of all create the response. So response equals this actingAs(user)->get(contacts) and then expect that response to contain contacts.data with a value of two. Now I'll just place response here as well so that that doesn't break and let's jump back into our test suite and run pest --dirty again. You'll see everything is passing which is absolutely perfect.
back into our test suite and run pest --dirty again. You'll see everything is passing which is absolutely perfect. Now let's just make sure this is the case. I'll switch this to one instead and rerun our tests and hopefully you'll see that test fail. Yeah. Property contacts.data does not have the expected size but look how clean that is. expect response to contain contacts.data to expect a response to contain and we could take this here right so I could take all of this here and I could cut it out and I could say to contain and then pass those properties in instead and now you can remove that as
