Testing newsletter subscription0:00
Let's write a test to verify that a User can be added to a mailing list. So you'll see I've set up a dummy Newsletter class here with a subscribeTo method and a var_dump to simulate some kind of network or some kind of cURL request, maybe to campaign monitor or something like that. So often when you're writing your test, you don't actually want to hit the code that makes the network request. It's very, very slow. So instead, you might have a single test that will verify that exchange. But anywhere else in your system, you'll mock that out. Okay.
But anywhere else in your system, you'll mock that out. Okay. Let's take a look. Now, in this case, we have a test route. It will resolve that Newsletter class out of the container, and then it will call subscribeToMembers and then provide the authenticated user. Okay. So that's going to add a user to the members newsletter list. Okay. So let's say, well, first we need a User.
Okay. So let's say, well, first we need a User. And next, I'm going to act as that User, so make them the authenticated User, and then perform a GET request to that endpoint. All right. So if we give that a run, we haven't written any assertions. But sure enough, you can see I am calling that method. Okay. Let's mock that out. Now, you may know that Mockery is included out of the box as a dependency of Laravel.
Mocking Newsletter with Mockery1:10
Let's mock that out. Now, you may know that mockery is included out of the box as a dependency of Laravel. So what I could always do is mock the Newsletter class, like so. And then I could say mock, and I'll show you a couple ways to do this. I could say, I expect a call to subscribeTo with, and this will be the argument list. So I expect a call to subscribeTo with members and with our authenticatedUser. Okay. And that should be done one time. All right. So ask yourself, does this fix the problem?
Binding mocks in container1:40
All right. So ask yourself, does this fix the problem? Well, we give it a run, and no. Mockery expected us to call this subscribeTo method, but it never took place. So how come? This will often confuse newcomers. Think about it. You mocked the Newsletter class, but you didn't update that instance in the service container. So when we make our GET request here, that's going to resolve the normal Newsletter instance. In fact, it'll instantiate it behind the scenes.
So when we make our GET request here, that's going to resolve the normal Newsletter instance. In fact, it'll instantiate it behind the scenes. Take a look. If I run this, we get our standard Newsletter instance. Nothing related to mockery there. Okay. So instead, what we can say is, look in the service container and update the instance for Newsletter. We can do it like this. Again, I'll show you a couple ways, and we'll keep tweaking this.
We can do it like this. Again, I'll show you a couple ways, and we'll keep tweaking this. So I could say, at the instance of newsletter will be our mock. So now, if we run it, we're going to get green, and that's because at this point, when we resolve newsletter out of the container, Laravel's going to look inside and see, oh, we already have something bound for newsletter, and it's the mock. So now, if we give this another shot, you'll see a bunch of mockery code here. And that's because, once again, we stored the mock in the service container. So now, when we resolve it out of the container, we don't get a new instance of newsletter. We instead get our existing mocked instance.
Alternative mock binding styles2:55
So now, when we resolve it out of the container, we don't get a new instance of newsletter. We instead get our existing mocked instance. Okay. So before we get to what's new in Laravel 5.8, let's talk about some other ways you might rewrite this. One small thing is, rather than calling app instance, perfectly fine, but you can also call an instance method directly on TestCase. And if we take a look at that, it'll be in interactsWithContainer. You'll see it basically does the exact same thing. So if we run it, it still passes.
You'll see it basically does the exact same thing. So if we run it, it still passes. Next, you can do it like this if you want, but you can also pass a closure here. So this would accept your mock, and we're basically going to inline this. I often like this approach. It makes it a little more contained. We're mocking newsletter, and we expect the mock to receive a call to subscribe to. Okay. Now, a quick note. We are expecting user here, but because we're in a closure, I have to explicitly pass it.
Now, a quick note. We are expecting User here, but because we're in a closure, I have to explicitly pass it through. But anyways, if I run it again, we get green. What else? One little thing here. You can specify the method you expect to be called as a string, or you can also make it like a pseudo method. mock should receive a call to subscribe to with this argument list. Run it again, and it passes.
Mock should receive a call to subscribe to with this argument list. Run it again, and it passes. But even better, we can now inline it like so. Should receive a call to subscribe to with this argument list. So that cleans things up a good bit, and we're still at green. Next, if you want, we could inline this. So I could say this instance for newsletter will be what we return from our mock. So we end up with something like that. And finally, that means because we no longer use that mock variable, we can clean this up, and here's what we get.
Laravel 5.8 mock helper4:32
And finally, that means because we no longer use that mock variable, we can clean this up, and here's what we get. And green. Okay. So now, here's what's new in Laravel 5.8. Yes, we have this instance method here, but take a look at some of these other methods on the trait: mock and spy. These are new in Laravel 5.8. As you can see here, this does almost exactly what we just wrote.
These are new in Laravel 5.8. As you can see here, this does almost exactly what we just wrote. It mocks something, and then it updates the instance in the service container. Okay. So let's see if we can switch over. That would mean I could say this mockNewsletter, I no longer have to do all of this code here, and now we'd get something like that. So let's give it a try. I run it, and we still get green. Cool.
I run it, and we still get green. Cool. So now we know you have a mock method for all of your tests, and when you call it, you give it usually the class you're mocking, as well as a closure for declaring what your expectation is. It cleans up your code a little bit, and it's one less thing to think about. I got to mock this, I need to set the expectations, and then I also have to update the instance within Laravel's service container. But now, if I switch back, you'll also see we have a spy method. So spies are similar, but the only difference is rather than setting an expectation on what
Using spies for assertions5:38
But now, if I switch back, you'll also see we have a spy method. So spies are similar, but the only difference is rather than setting an expectation on what should happen, a spy works in reverse. You do whatever you need to do, and then the spy kind of kept an eye on everything. So then you can say, well, I expect this to have been called. In many ways, it just changes up the order to be a little more developer-friendly, or more readable, I would say. The only difference is this spy. So we're going to spy this class, and then we can comment this out. We can perform whatever action we need to, whether that's, in this case, making a GET
So we're going to spy this class, and then we can comment this out. We can perform whatever action we need to, whether that's, in this case, making a GET request or you're instantiating an event listener, whatever you need to. You perform your action, and now, after it, you can say, well, a spy should have received a call to subscribe to, with members, and with the user. Okay, so fix that. Now if we give that a run, we still get green. But we're using a spy in this example rather than a mock. I find these days I more frequently use a spy and less often a mock, but it's entirely up to you.
We don't do that until this code executes. So you need to make sure, if you're taking the spy approach, create your spy, and don't forget, we'll update the instance of that spy or that class in the service container so that you don't have to deal with it. Then perform whatever action you need to and then ask your spy to confirm what was called. Now one other nice approach to this is because we no longer have the closure to deal with, I could inline this here, and then we end up with something like this. Create your spy, perform your action, and then confirm what was called. So that returns green, but yeah, if at any point you don't make that call, of course it's going to blow up, hey, I expected this call to subscribe to, but you never performed.
