تماشای این درس نیاز به اشتراک حرفه‌ای دارد.

Plan User Notifications0:00

Let's have a look at the track command one more time. So if the goal is for this command to run, say, every five minutes, then at some point we do want to notify the user when the stock changes in a notable way, right? So if you want to buy a Nintendo Switch and it's out of stock, but then you run the command and it's now in stock, we of course should notify the user in some way, through an email, through a text message, even a Slack notification. Let's figure out how to do that. I'm going to return to the test for our track command, and we'll add a new one here. It notifies the user. So real quick, in this case, we're building it for ourselves, it's a little console application,

It notifies the user. So real quick, in this case, we're building it for ourselves, it's a little console application, I am the user. But you can imagine if you converted this to a web app, well maybe any user who signs up can subscribe to specific products. And then we'd tweak the code to notify only the subscribers of a product when the stock changes. Okay, we're going to keep it simple. It notifies the user when the stock changes in a notable way. My thinking is if it was out of stock, but now in stock, that's notable.

It notifies the user when the stock changes in a notable way. My thinking is if it was out of stock, but now in stock, that's notable. And another one might be if it was $500, but it's now $400, that too is a notable change. It's now $100 cheaper. Okay, so let's see, given I have a User, and also a Product, when I track that Product, if the stock changes in a notable way after being tracked, then the User should be notified. That's basically it, I think. Okay, let's get started. I'm going to begin by whipping up a factory for a User. And we'll set the email to myself at example.com.

I'm going to begin by whipping up a factory for a User. And we'll set the email to myself at example.com. All right, given I have a User and a Product, let's use our Seeder here. Next, when I track that Product, right, we're going to use our artisan command. However, once again, I don't want to hit a client, I don't want to hit the Best Buy API. So we're going to fake it. And notice how, yet again, I'm copying this and pasting it. I think we have three or four uses of this exact code now. So this is a good use case for extracting it either to a method on the test base class, or even a trait that you can pull in.

So this is a good use case for extracting it either to a method on the TestCase class, or even a trait that you can pull in. And that way, you can mock out the client with a single method call. Let's make a note to do that pretty soon. Okay, when I track that product, if the stock changes in a notable way, then the user should be notified. So we have two different ways to notify a user. We can send them a mail, or we can send them a notification. Both of them can send an email. The only difference is, a standard mail is an email.

Testing Notifications with Fakes2:54

Both of them can send an email. The only difference is, a standard mail is an email. But a notification could be an email, or it could be a text message, or it could be a Slack notification, or it could be a database notification that you display in a flash message, or it could be all four at the same time. So with that in mind, if I might notify the User through a text message and an email, I'm going to stick with the notification option. So here's how we test notifications. I can start by saying notification()->fake(). And do note, we pulled in the facade... where is it?

I can start by saying notification fake. And do note, we pulled in the Facade... where is it? Right there. So facades, at least in the past, were shamed a little bit as being a poor architecture choice. Which is funny, because I think of them as one of the most innovative features of Laravel. So among those, if we go to the Notification Facade, you'll see a fake method here. But also notice it calls the swap method on the parent Facade class, which means swap is available to every Facade in the application, including the ones you create, and also including real-time facades, which is crazy, it's really neat. Which means you can also do things like this, facade should receive, and this will programmatically

real-time facades, which is crazy, it's really neat. Which means you can also do things like this, facade should receive, and this will programmatically swap out the underlying instance with a mock. So it's very, very cool what you can accomplish with these. Anyways, in our case, when we call that fake method, that's going to ensure that you don't send a real notification, and it's also going to swap out the underlying instance with this NotificationFake class. And this has a bunch of assertions directly on the object that you can call. In our case, we want assertSentTo. I want to assert that after we track a Product, then a notification will be sent to the given.

In our case, we want to assert sentTo. I want to assert that after we track a product, then a Notification will be sent to the given User. Okay, let's come on back, and let's see what we can do here. Notification, and yeah, at the moment, nothing is being sent. So just to show you, I could assert that nothing is sent when I run this code. I imagine that should return green, and yeah, it does. But of course, we actually want to send an email. So let's assert that it was sent to the User. How about now in stock?

Create Stock Update Notification5:24

So if we run it, of course it's going to fail, because one, we don't have a notification of that name, and two, it's never sent, even if we did. Let's get started. php artisan make:notification called ImportantStockUpdate. Okay, so now, you'll find this class in a new notifications directory. And if I scroll down, notice we can specify the delivery channels, or the transports. So this, again, would allow me to send an email, and a text message, and a Slack notification, as well as anything else. It could even be a postcard, believe it or not. Probably not the most efficient thing.

up our email without writing up all of the HTML, which is cool. So I could set the subject here to important stock update, and then we'll fill that in later. Okay, so let's come back to our track command. I'm going to import this. But of course, if I run it, it's still going to fail, because we haven't yet fired off that email. Okay, so let's figure this out. Let's go down the path here. When I call this artisan command—and actually, real quick, if it ever gets confusing, remember,

Let's go down the path here. When I call this artisan command—and actually, real quick, if it ever gets confusing, remember, the artisan command is like a controller for the console. It serves the exact same purpose. It's the entry point from the console. A controller is the entry point from the web, from HTTP. Anyways, if we handle this, we grab all products, and for each one, we track it. A product is tracked like this. So we delegate to the Stock model, and a Stock model is tracked like so. So I want to point your attention right here.

Fire Now-In-Stock Event7:16

So we delegate to the Stock model, and a Stock model is tracked like so. So I want to point your attention right here. This is where we update the current stock, and just as a quick refresher, this is what stock for an item looks like. We store the current price and the availability. So maybe we could check, well, if the price is lower than it was before, or if it was out of stock and it's now in stock, that's a notable event. And I just used the word event, so maybe we should fire an event. Let's figure this out. Before we update it, maybe I could say, if the stock was not in stock, if it was not.

Let's figure this out. Before we update it, maybe I could say, if the stock was not in stock, if it was not in stock, but the status, this is what's returned from our API request, if that status is now available, that means we were out of stock and now we're in stock, and that's a notable event. So we could fire off an event. So why don't we call this, yeah, again, we could make it very specific, like nowInStock. We could make it a bit more generic if we need to. I'm not sure. For now, we'll stick with nowInStock, and then we'll pass through the current stock.

I'm not sure. For now, we'll stick with now in stock, and then we'll pass through the current stock instance. Okay, so if I give this a run, it's going to fail because it doesn't know what that is. All right, let's create an Event. Now in stock, and I will import that. Next, let's open this up, and it's going to accept a Stock instance. And we'll initialize that and make it public. All right, let's see how we're doing here.

Add Listener and Auto-Discovery8:48

And we'll initialize that and make it public. All right, let's see how we're doing here. Run it again. We've now fired that event, but we didn't do anything with it. We're still not sending a notification. So let's do that now. Make a listener called SendStockUpdateNotification. Okay, so now if we open this up, let's go into our listeners directory. There it is. We're going to handle the event, but notice I still have to add the type.

There it is. We're going to handle the event, but notice I still have to add the type. So here's a little tip. If I delete this, and I try it again, but first, we take a look at the help, you'll see there is an option to specify the associated event. And when you do that, what is the event? Now in stock. Anyways, when you do that, it'll populate, it'll update the stub to type hint this. It's just a convenience. Anyways, now, remember, we only have ourselves in this application, so I can stick with User.

It's just a convenience. Anyways, now, remember, we only have ourselves in this application, so I can stick with User first. But if you have multiple Users, you would grab the subscribers to that product. Okay, User first, notify them that we have, what is it? What do we call it? Important stock update. And that's it. So we can get rid of this, reformat, and there you go. All right, let's give it a run, but I think, yeah, it's still going to fail.

So we can get rid of this, reformat, and there you go. All right, let's give it a run, but I think, yeah, it's still going to fail. Here's a little tip. We have an event, we have an event listener, but we haven't registered the event listener. So you can either do that manually, or visit your app provider's EventServiceProvider, and scroll down here, and we're going to override a method called shouldDiscoverEvents. So I believe this is set to false. But if you set it to true, Laravel is going to automatically discover any of your events and listeners. And it's kind of cool how it does it.

and listeners. And it's kind of cool how it does it. It's going to look at your listeners, and it'll see that we are handling nowInStock. So it has everything it needs. It has the listener class, and it also has the event class that we're listening for. So behind the scenes, it'll programmatically wire up the events and their respective listeners. Okay, so now if I run it again, it returns green. It works. But I think we can be a little bit more specific. Let's come back here.

But I think we can be a little bit more specific. Let's come back here. We're not really testing this logic. So let's go back to TrackCommandTest, and let's have a look here. We have our seeder, and that is setting the inStock status to false, which is good. However, if we were to set available to false, this test should fail, and that's what we want. Yeah, so it is in fact working, but I want to make that a bit more explicit. So why don't we say it notifies the User when a stock is now available, something like that. So let's say, given I have a User and a Product that is out of stock, if we update that stock

So why don't we say it notifies the User when a Product is now available, something like that. So let's say, given I have a User and a Product that is out of stock, if we update that stock and it's now available, then we expect an email to be sent. Give that a run, and it works. So let's duplicate this to make it extra clear, and then we'll clean it up. It does not notify, let's see, when the stock remains unavailable. It's kind of weird. That'll get the job done. So the only difference here would be, we have a Product that's out of stock, we fetch an update, but it's still out of stock.

So the only difference here would be, we have a product that's out of stock, we fetch an update, but it's still out of stock. So in that case, I assert that nothing was sent. All right, so that one passes, and I think everything should pass here. Oh, we have an issue. Did this one now fail? Oh yeah, it does, because we're now assuming that we always have a User. So why don't we do this? Let's just assume any time we seed our database, we will also whip up a User.

Let's just assume any time we seed our database, we will also whip up a User. Yeah. And then let's clean that up. Okay, so now, if we switch back here, yeah, I no longer have to create that User. And same here, but I will have to track down the User. So let's just say, we expect an email sent to that first User we created. All right, did that fix it? Yes, it did. Okay, cool. Okay, so now I think we can remove a little bit of duplication.

Refactor Test Setup Helpers13:08

Okay, cool. Okay, so now I think we can remove a little bit of duplication. We're going to start with this bit of code here. What if instead we just said something like, mockClientRequest? And then here, we can specify whether it's available, or what the price should be. Okay, let's remove that. We run the code. It's going to fail. So we can add that, mockClientRequest, available, and the price. And then that's going to do this code here.

So we can add that, mock clientRequest, available, and the price. And then that's going to do this code here. And then let's just update this, and that. All right, did that fix it? Yes, it did. So now I can delegate to this method, and we can do the same thing. The only difference is, available should now be true. But notice I'm setting the price again. Maybe that should have a default, and that way I don't have to set it every single time. So that gives us this, and this.

Maybe that should have a default, and that way I don't have to set it every single time. So that gives us this, and this. Next, maybe the same is true for available. So if you just call mockClientRequest with no arguments, we'll assume it's available, and the price is some arbitrary number. We get that, that, yeah, let's give that a shot. And it still works. However, we're also referencing that in other classes, aren't we? Let's have a look. Yeah, we're doing it here.

Let's have a look. Yeah, we're doing it here. So we have a couple choices again. We could go to our TestCase. We have ownership of this, and we could move it there. So let's come back and see what that looks like. Right here. Let's move it up to TestCase. Okay, so now you'll find it right there. Which means on any of these unit tests, I can now say this, mockClientRequest.

Okay, so now you'll find it right there. Which means on any of these unit tests, I can now say this, mockClientRequest. And let's see, is the price relevant? Does it have to be 99? I don't think so. So can I stick with that? Hmm, undefinedVariable. Yeah, in this case, it's useful having that. So maybe I will be explicit. That works, okay.

But if you wanted to, here we could add the setUp method. And then what we could do, it looks like every test runs the seeder, right? Seed, seed, seed, yep. And we also want the test to fake the notification. So I could do this. And just remove a little bit of that initial setup work. And, oh, we have one more of these, don't we? Let's go ahead and mock that. And no args, does that still work?

Let's go ahead and mock that. And no args, does that still work? Yes, it does. Okay, so now that looks good. We no longer need to fake and seed here, or fake and seed here. Yes, we'll have a look now. That's still passing. And if we reformat, here's our updated test. So for every test, we'll fake the notification

Format Notification Email16:57

And if we reformat, here's our updated test. So for every test, we'll fake the notification and seed the database. Then we have it tracks products, good. It does not notify when the stock remains unavailable, but it does notify when the stock becomes available. So not too bad. Finally, the only remaining step would be to format this email. And to do that, we of course need access

would be to format this email. And to do that, we of course need access to the stock that was updated. So let's accept that. We'll initialize it. And then let's just make sure in our listener that we pass in the relevant stock. Great, so now anything related to the stock can be accessed. So for example, I could say for, and then we have the stock.

So for example, I could say for, and then we have the stock. And then down here, action would be like a button you click. Buy it now. And this would be a URL to back in the stock table right here. Something like that. And now real quick as a demo, if you want to see what this looks like, let's just temporarily update to this.

if you want to see what this looks like, let's just temporarily update to this. Return new important stock updates. And then let's just find the first piece of stock in our database. And then we want the mail version of this notification. So to mail. Oh yeah, we have to give it the notifiable. Let's just quickly whip up some random User. Okay, I think that should work.

دوست دارید گاهی خبرهای Laracasts را ایمیل کنیم؟