Setting Up Package Tests0:00
So, now that we have our package scaffolded, let's finish up by writing some tests and deploying it. So, I'll create a new directory here for our tests, and then we'll add a new one, and WidgetTest, and I have a little snippet I use here. Now I don't think we'll need migrations here, but next, rather than extending TestCase, well, remember, this isn't a Laravel application, this is a package we're testing. So again, that's where orchestra comes into play. If we go to the composer.json file, you'll see it's included out of the box. So let's go ahead and install those dependencies. Eric has Widget, composer install.
So let's go ahead and install those dependencies. Eric has Widget, Composer, install. Okay, great. So now, if I switch back to our test class, I'm not going to extend that test case, we'll instead extend the Orchestra version. Okay, so let's begin with the most basic thing. I want to make sure that the directive itself, so when you do something like this, I want to make sure that compiles to the proper php. So we'll say it compiles the Widget Blade directive. Okay, so let's see what we want.
So we'll say it compiles the Widget Blade directive. Okay, so let's see what we want. If I had this in a view, or in fact, I think we're going to do the full path. So we'll say Laracast, WidgetTest, and then I'll have a testWidget here, and I'll need to create that. But yeah, if I did something like that, ultimately, what do I want that to expand to? I think it should probably be something like this. Let's see. And then I think two episodes ago, we called this method loadView. I don't like that.
And then I think two episodes ago, we called this method loadView. I don't like that. I think we're going to change that, but we'll hold off until the tests pass before doing it. But yeah, I think ultimately, that's what it needs to expand to. So what we could do is say, okay, well, here's the string we're working with. And here is the expected output. But now how exactly do we compile it? Let me show you this. Let's go back to our project root.
Compiling Blade Directives1:59
Let me show you this. Let's go back to our project root. There it is. And if I open up php artisan tinker, what we can do is resolve the Blade compiler out of Laravel's service container. And then what you can do is compile any string you give it, as if it was reading a view. So for example, if I said, if true, it works, you know, just something to work with. And sure enough, that blade directive will expand to this PHP code. Okay, so that's really all we need here. Resolve the Blade compiler.
Okay, so that's really all we need here. Resolve the Blade compiler. Then compile this given string. And that'll give us the compiled output. And then we can say, all right, I assert that the compiled string equals what we expected. And yeah, I think we're ready to give this a shot. So let's cd back into our package directory. And then I can run phpunit locally here. All right, so it does fail. We expected it to expand to that, but it looks like it's been unaltered.
Bootstrapping Service Provider2:57
All right, so it does fail. We expected it to expand to that, but it looks like it's been unaltered. And this is because if we're using orchestra, we need to introduce the WidgetServiceProvider. So let's do that now. And I believe it's getPackageProviders. Yeah, there it is. Okay, so all we have to do is return any extra service providers that we want to be included as part of the request. In this case, it would be WidgetServiceProvider. All right, so now if we give it another shot, it should read and bootstrap that provider.
In this case, it would be WidgetServiceProvider. All right, so now if we give it another shot, it should read and bootstrap that provider. Okay, so it fails again, but we're getting pretty close here. So again, I expected this, but it looks like it expanded to effectively the same thing. Let's just update this. And then we can remove the echo. And let's give it another run. Okay, what is wrong here? Oh, yeah, of course, the namespace is wrong. So I'm going to change this up.
And then I think we can just pull in the expression there itself. Because remember, in this case, the expression will end up being something like, within quotes, the full path to your widget. So we can resolve that path, and then call loadView. All right, let's give it another shot. There we go. Now we're getting green. Okay, so now we can do some other things. We can dig a little bit further. For example, it chooses a default view name based on the class.
Testing Default View Naming4:39
We can dig a little bit further. For example, it chooses a default view name based on the class. Okay, so yeah, here, we're going to set up a testWidget. And yeah, often when I set up these little doubles, I will do it directly within the class. Okay, so now we have something to work with. I will instantiate that. And we know if the widget name is testWidget, then the default view name should be testWidget like that. So let's say this assertEquals testWidget.
like that. So let's say this assertEquals testWidget. And you know what? Let's have a look. We have these methods like view, but you'll see that it calculates the view name as part of it, and then it returns a call to the view function. So maybe we should extract that to viewName, something like that. Okay, so now, if we give that a run, call to undefined method viewName. Okay, let's fix that. So it sounds like this section here will now be extracted to its own method called viewName.
Okay, let's fix that. So it sounds like this section here will now be extracted to its own method called viewName. And now if we scroll down, that could be public. And let's clean that up. All right, run it one more time. And there we go. We're back to green. So we can grab all of that. Yeah, we're going to do a little bit of work here. Okay, so now we have a method to calculate the viewName.
Rendering Widgets in Tests6:33
Okay, so now we could say, we'll set up an example here for the double. And how do we want to test this? Well, it sounds like when we render the widget. So when we render that view, we should see a test widget for the title. So here we can just do a simple scan of the HTML. Do we see the string test widget? If so, we're good to go. Okay, so I did see render there, and that's what I'm thinking. So you'll see in this Widget class, we had a method called loadView. But I don't like that at all.
So you'll see in this widget class, we had a method called loadView. But I don't like that at all. But you can see what it's doing is it's returning a view with the necessary data, exactly how you would in a controller. So what I'm thinking is instead, we could call it render. And in fact, maybe it could even be a static method. So what if I said testWidget::render, and that would render the HTML. So if we took that approach, I could say assertStringContainsString. The needle is testWidget, and the haystack is that block of HTML. Okay, so let's run it again, but
The needle is testWidget, and the haystack is that block of HTML. Okay, so let's run it again, but now it's going to fail because there is no method render. Okay, so before I get rid of this, what I think I'm going to do is make another one here, and that would instantiate a new instance. And then we could call loadView. Yeah, we're just going to take this in a few steps. Okay, so if I run that again, yeah, okay, so now it's trying to load that view. But in our case, we don't really care how do we want to do this.
now it's trying to load that view. But in our case, we don't really care how do we want to do this. So how do we reference the view? Because by default, it's going to look in a certain directory. And then maybe we can override the view function. And that way, rather than doing view(), that's going to look in a specific directory for the view.blade.php file. I can instead do viewFile(). And what's nice about this is I can store the view anywhere I want. I just have to give a file path to where it should be.
And what's nice about this is I can store the view anywhere I want. I just have to give a file path to where it should be. So in this case, I could maybe create a stubs directory like this. And I can say that is the file path I want to do. All right, so we could create a directory, stubs, and then a file for our test widget. And then, yeah, here, whatever we have there. So yeah, let's give it a shot. I'll run that whole file again. And we're getting a memory issue, so something's being called recursively.
I'll run that whole file again. And we're getting a memory issue, so something's being called recursively. Yeah, so like if we say render, I bet we're going to see it over. Yep, it's being called over and over. So here's my guess. It's going to be related to this section here where we build up the view data. I think, yeah, we're going to have to include it. But also, I think we may change this, because I don't really like what's going on here. But if I run it again, mm-hm, that fixes the problem.
this is available in the rendered view. And in fact, if you want to see what that looks like, testWidget::render(). Let's run it. Yeah, this is going to be a big string, so let's go to the top. It's a View instance. If I convert that to a string and run it again, you will see the compiled widget. So our test is simply looking at that compiled HTML, and
If I convert that to a string and run it again, you will see the compiled widget. So our test is simply looking at that compiled HTML, and it's making sure that it does see testWidget there. Good enough for me. Now maybe we need to do the same thing for methods. So I could say all public methods are available to the view. So once again, we'll have a method called items, and that will return item1 and item2. Now, if we compile it, I should see at the very least item1 and then item2 as well. And let's see, I guess we should save that to a variable.
Now, if we compile it, I should see at the very least item1 and then item2 as well. And let's see, I guess we should save that to a variable. There we go. All right, let's give it another run. And it's failing here, but only because our testWidget step here doesn't make use of it. So I'll do that now. For each items as item, spit out the item. All right, let's run it again. And now that's returning green as well.
All right, let's run it again. And now that's returning green as well. All right, so there's more to do on the testing front, but that at least gives you an idea of the basic workflow you might follow. And think of the assurance we now have. For example, if I were to change something up or remove that, your test would instantly notify you, hey, something's going on here. Your code's no longer working the way it did before. Or if you change the logic here, maybe you switched a snake_case, forgetting that it changes your expected output.
Or if you change the logic here, maybe you switched a snake_case, forgetting that it changes your expected output. All right, so now at this point, I want to finish up. Let's make sure, still at green. Yeah, let's finish up by cleaning up the code a little bit and then pushing it to GitHub. Now, I already told you, I didn't like that name, loadView. So why don't we instead make this the work of the render method? So let's do this. Let's create our instance, and then I can say, rather than loadView,
So let's do this. Let's create our instance, and then I can say, rather than loadView, we'll just write it out, buildViewData. Yeah, something like that, which should mean I can get rid of this method entirely. All right, but if I run it again, yeah, it is working. However, if we go back to our test, this is one thing. So this test is passing, but it's not what we want anymore. So we may need another test to actually load a test view to make sure it works. So in this case, yeah, we did make sure it compiled this php code, but if that was executed, it would fail.
So in this case, yeah, we did make sure it compiled this php code, but if that was executed, it would fail. So in our case, we're not going to do that anymore. Here's what we'll do instead. We're just going to resolve it out of the container and nothing else. And I'll tell you why in a second. But if we run it, it is going to fail. So we go to our provider, and we fix the issue. Run it again, that's back to green. But yeah, now, take a look at this.
Run it again, that's back to green. But yeah, now, take a look at this. I could say down here at the bottom, it renders itself when converted to a string. So if I interact with a widget instance as if it was a string, it'll be rendered. So I could say this assertStringContainsString. We'll look for testWidget one more time. But all I'm going to do here is instantiate our testWidget and nothing more.
But all I'm going to do here is instantiate our TestWidget and nothing more. All right, let's see if it works. No, it fails. So the issue is, we expected a string, but we got an object. However, we can tell any object what to do when being cast to a string. So I could say right here, all right, if we try to echo the object as an example, all we're going to do is render it, almost exactly what we had before. So take a look at this, buildViewData. And then let's not forget to convert that to a string, and then add this here.
So take a look at this, buildViewData. And then let's not forget to convert that to a string, and then add this here. All right, so if we give that a run, yeah, now that works as well. Which means, one more time, our render method can move down here. And we don't want to double up. So now, all I have to do is just instantiate the widget instance. And that's it. At least I think, if I run it, yeah, that returns green. So that is simply a helper. Okay, let's keep going, loadView no longer exists, so
So that is simply a helper. Okay, let's keep going, loadView no longer exists, so I don't have to worry about that. All right, what else? A couple more things. Build viewData, let's keep it simple, we'll just call it viewData. All right, next, this section here where we make all public methods available to the view, I'm not sure on that one. It's kind of cool, but I think it might annoy you. Because, for example, here's our testWidget.
It's kind of cool, but I think it might annoy you. Because, for example, here's our test widget. This is neat, but what about situations where you want to pass a argument that will determine how you return the data? It wouldn't allow for that. Next, what if you wanted a method that wasn't a getter, but it was some kind of helper function that you wanted? Well, again, you couldn't do that, because you're not getting that method call passed to the view. You're getting the returned value from that method call.
because you're not getting that method call passed to the view. You're getting the returned value from that method call. So yeah, I'm thinking it could be kind of annoying. So we have a couple options. One, we could just make, so if items was available to the view, in situations where you have a method, you just have to call it. We could do that. We could follow a convention, sort of like the way Laravel and Eloquent works. So maybe if I want this available as a variable, it could be getItems. Like maybe you have to precede it with get.
So maybe if I want this available as a variable, it could be getItems. Like maybe you have to precede it with get. Or that's kind of what Eloquent does. You force the user to follow some convention to make it clear. So at least for now, what I'm going to do is the method will be available to the view, but it will be available as a closure that you can call. So with that in mind, what I want to do is if we come back up, we will have to call it kind of like that. And I don't love it, but I'm not sure about an alternative yet. So I'll think on that on my own.
Publishing Package to GitHub16:10
We have to give it to the object, same thing as before. All right, so I hope that fixes it, and it does. So a small tweak there, but it might just give you a little more flexibility and control over your methods. All right, nonetheless, I think this is good enough for a small tutorial here. The next step is to push to GitHub. And here we are on GitHub. I'll create a new repository. Okay, and next, if I switch back, let's go back to our root. Now, I believe there's a helper here, yeah, Packager::publish.
Okay, and next, if I switch back, let's go back to our root. Now, I believe there's a helper here, yeah, packager:publish. Publish your package to GitHub with git. Okay, so all we need is the vendor, the name, and the URL to the GitHub repo. Now, we haven't instantiated git yet, and this command will do that for us. But before we do, let's make sure in our widget directory, I'm going to go ahead and create a .gitignore. And at least to start, we're going to ignore the vendor directory. All right, so let's do it. packager:publish, our vendor name is Laracast, we called it widget.
All right, so let's do it. php artisan vendor:publish, our vendor name is Laracasts, we called it widget. And then I'll paste in a link to that repo we just created. Give it a second, and there we go. So it should now be live on GitHub. The only remaining step is to submit it to Packagist. And then at that point, it's available to anyone in the world. So let's give it a refresh, and there's your package.
