Stubbing Return Values0:00
So now, consider that the create method on this gateway dependency returns a value. In real life, it might be an object, let's just say it returns your receipt number, or ID. And then perhaps later, that receipt number is used when you fire off the welcome email. Okay, so if you don't actually want to hit the Stripe API, how do you figure out the receipt number? This is where stubs come into play. If we come back to our test, this is what we worked on in the last episode. Let's once again extract this to a gateway variable. But in its current form, any method on gateway is just going to return null.
Let's once again extract this to a gateway variable. But in its current form, any method on gateway is just going to return null. It doesn't do anything at all. We're going to change that. This is where it turns into a stub. I could say gateway, and specifically the method called create, that, when called, should return, how about receiptStub. So in a technical sense, this right here is what turns a basic dummy object into a stub. Have a look here. I'm going to switch back to our subscription.
Have a look here. I'm going to switch back to our subscription. And if I simply, real quick, dump the receipt, let's give it a run. And sure enough, we were able to dynamically declare what should happen, and more specifically, what should be returned when you call that create method. Very cool. Now once again, let's come on back. You can do it this way, or you can do it manually. So we already did this in the last episode, but we're going to do it one more time. We'll set up our class, and we're going to call this GatewayStub.
Building a Stub Class1:31
So we already did this in the last episode, but we're going to do it one more time. We'll set up our class, and we're going to call this gatewayStub. And I'll just do it here. We're going to implement the Gateway class or interface. Okay. But now, I don't want this to be a total dummy that does nothing. I want this to return receiptStub. And again, we have a stand-in for the real thing. Okay. So now back in our test, we're going to replace this with our gatewayStub and reformat.
Okay. So now back in our test, we're going to replace this with our gateway stub and reformat. Now if I run everything again, it works. And actually, let's go back to subscription, bring back that dd, and you'll see we once again get receipt stub. Same thing accomplished in two different ways. Okay. But I'm going to keep it automated using phpunit. Bring this all back. There we go.
Testing Email Delivery2:22
Bring this all back. There we go. So for this particular test, I'm not sure it's needed, but we're going to do a new test. Let's focus on that email Idea. So let's say it delivers a receipt after you create a subscription, because really, this is kind of turning into like a service class or maybe a use case class. You might even call it createSubscription with a handle method, you know, something like that. I'm just keeping it super simple. But yeah, this is effectively a service class.
I'm just keeping it super simple. But yeah, this is effectively a service class. So anyways, if we want to declare that when we create a Subscription, it should deliver the receipt, let's just grab all of this. And notice I'm okay with the duplication when we're in the early stages. Once I get to around the third method, that's where things start to become clear. And I can see, okay, some of this can go into the setUp method because I do it for every test. And some of this other stuff could go to a helper method so that I'm not repeating myself over and over for every test.
And some of this other stuff could go to a helper method so that I'm not repeating myself over and over for every test. Okay. But anyways, it delivers a receipt. So let's say, we'll bring this out, let me introduce a variable called gateway, given I have a User, I don't need any of this. But when I call a create method, let's say, I expect that an email should be delivered with the receipt ID. So now I want you to focus on this word expect. This is a really important term, and it changes things once again.
Mocking Mailer Expectations4:06
send. That's when we get into mocking territory. Let's see how to do it. First, subscription now needs a mailer. So let's create one now, mailer. This will be a mock for our mailer class. That doesn't exist. So let's go ahead and create it. We'll give it our app namespace and the source directory. OK, so this will end up having some kind of deliver method.
We'll give it our app namespace and the source directory. OK, so this will end up having some kind of deliver method. Let's come on back. You'll see right here it's squawking, though, because subscription currently only has one dependency. So let's update the signature to accept the mailer as well. In order for this service to work, you have to give us your payment gateway as well as a mailer dependency. OK, now I expect that an email should be delivered when I call the create method. So let's say mailer, and I'm going to say we expect, and notice this invocation rule,
OK, now I expect that an email should be delivered when I call the create method. So let's say mailer, and I'm going to say we expect, and notice this invocation rule, how many times? Well, I expect one call to the method deliver. And specifically, I want that method to receive the following arguments, and maybe we're going to keep it super simple. When you call mail deliver, this is your receipt. We're just going to pass the message as a string to keep it very, very simple. So how about your receipt number is, and then we'll say receiptStub in reformat. All right, are we all on the same page?
So how about your receipt number is, and then we'll say receipt stub in reformat. All right, are we all on the same page? Let's give it a shot. I'm going to run phpunit again on the test directory. I want colors in the output, but I'm only going to filter it down to this specific method. It's the only one I care about right now. All right, notice expectation failed. We expected it to be called one time, but actually it was called zero times. All right, so think about it. Why is this failing?
All right, so think about it. Why is this failing? Let's go back to it. We said we expect mailerDeliver to be called with this message. Let's now go over here. Nope, it never happened. So let's make a pass. This mailerDeliver, and what did we... Let's just grab that right there. And then we'll make the receiptId dynamic, like so.
Let's just grab that right there. And then we'll make the receiptId dynamic, like so. Now here it looks like, yeah, deliver isn't accepting anything. So let's just do that automatically. We'll call it message. Okay, so now our deliver method accepts the message. Let's run our test again. Ah, it fails. So here's what we expected, but actually it didn't include any receiptId. Okay, so notice right now our gateway returns null.
So here's what we expected, but actually it didn't include any receiptID. Okay, so notice right now our gateway returns null. That's what a dummy does. It accepts a call to that method, but it doesn't do anything with it. So let's make it a stub, like we learned about earlier. Let's do this separately, and then we'll clean up. Gateway method create will return receiptStub, like we did earlier. Okay, we run it again, and now it passes. So now think about it. If at any point you change this up or you forget to pass the receiptID or anything.
So now think about it. If at any point you change this up or you forget to pass the receiptId or anything like that, your tests are not going to fail. It'll say, hey, we expected this message to be sent, but it never happened. But next, if we come back to our test, a few things. Let's organize this a bit better. Here's where we set up our gateway. Here's where we set up our mailer and its expectations. Here's where we instantiate the class under test. And then finally, we can inline this.
Here's where we instantiate the class under test. And then finally, we can inline this. This is where we put things into play. This is where we start the car. When I call create, we expect this expectation to run. So notice in this case, there is no assertion for the test. What the test does is it makes sure that as part of this create method, a message is sent to deliver on mailer, and everything passes. However, if we bring it back, be careful. We've changed the signature, which means that first one's not going to fail.
Dummy vs Stub vs Mock8:56
All of those fit within the category of a test double, which is a substitute for a real object. Now, we've learned that in phpunit, you can create a test double using the somewhat confusingly named createMock method. Again, don't forget, sometimes people use mock to refer to all of these, which honestly is fine. This is sometimes how language develops. If you want to use mock as an all-encompassing term, then, you know, who cares? And yet other people like to be very specific about the differences. Now we've also learned how we use this determines what the technical term would be.
And yet other people like to be very specific about the differences. Now we've also learned how we use this determines what the technical term would be. So for example, right up here, we needed a mailer for this Subscription class. Now for the test we were writing, we didn't care what happened to it, or what methods are called, or what's returned. So in that case, it's a dummy. We just needed to fill the parameter list. However, down here, we do the same thing where we set up our mailer, but for this particular test we needed to assert that a deliver method was called on it. So this is what would change that technical term from a dummy to a mock.
test we needed to assert that a deliver method was called on it. So this is what would change that technical term from a dummy to a mock. A mock will include expectations. If you can say, I expect that this dependency and that method is called, then you're dealing with a mock. Now right up here, this one is a little bit different. This isn't a mock. We have no expectation here. We're just saying, look, if a create method is called, this is what I want you to return. But we're not saying a create method has to be called, and that is the difference.
We're just saying, look, if a create method is called, this is what I want you to return. But we're not saying a create method has to be called, and that is the difference. And it's an important difference. So here, this would be called a dummy, and this would be called a dummy. Down here, this would be referred to as a stub, and this would be thought of as a mock. It's kind of confusing. It all depends on the usage, or you can just call them test doubles. Nobody's going to beat you up either way, but it's still important to understand the basic differences. Okay, in the next episode, I want to talk to you about the dangers of this approach.
