Testing Subscription Creation0:44
my billing provider. I have to get you subscribed on their end. So at some point, there does need to be an API request, an HTTP call. But on top of that, we also need to update our local User record. Maybe you change a field or two to mark them as subscribed. And then maybe you dispatch an event or you fire off a welcome email. Thanks for signing up. We're glad to have you. These are the sorts of things that need to happen when a subscription is created. So if you were to write a test for this, let's do that now. We are testing subscription. We'll just call it SubscriptionTest. And let's put this in the example6 folder. Okay, yes, we would have a test that says it creates a Stripe subscription. But think about it. That is a time-consuming task. Every time you hit this create method, you're going to perform an API request. And that could take a second or two seconds or three seconds for every
is a time-consuming task. Every time you hit this create method, you're going to perform an API request. And that could take a second or two seconds or three seconds for every single time you call this method. You don't want to do that. But it does raise the question, should we test that functionality at all? Should we test that when I call create, an actual subscription or customer or a subscription record on StripeSend is created? Is that something you want on your test? And the answer is, absolutely. If that aspect of your application doesn't work, you're in big trouble. You need to make sure it works. But do you need to perform that call every single time you create a subscription in one of your tests? And of course, the answer is no. So it sounds like let's write the minimum number of tests. And maybe that number is one. The minimum number of tests to ensure that, yes, when
And of course, the answer is no. So it sounds like let's write the minimum number of tests. And maybe that number is one. The minimum number of tests to ensure that, yes, when I call this method, we create a Stripe subscription. But for every other test I write, let's just use a stand-in or a dummy. I don't want to really interact with Stripe, but I still need a stand-in for that interaction. This is what a dummy is for. Let's play around. Let's go back to SubscriptionTest. I'm not really going to write this test here because we don't have Cashier set up. If this was a Laravel project, I don't have the Stripe API available. So what I'll do here is say mark as skipped, and then we can come back to it. OK. So the other thing we need to do is update the user's local record or on your production database. So creating a subscription marks the user as subscribed. How about that? OK. So given
Asserting User Subscribed3:08
other thing we need to do is update the user's local record or on your production database. So creating a subscription marks the user as subscribed. How about that? OK. So given I have a subscription, and when I call create on it, what do I expect to happen in response? And actually, real quick, notice when I call create, we need to give it a user. So let's do that now. User, this is all in memory for the demo. But that's OK. A user has a name of John Doe, and I'll let you take a look at it. Not an Eloquent record or anything. It's literally just a user where we store the name in memory. OK. So after I call create, the result is I expect the user to now be subscribed. Right? So why don't we make that an assertion? That should now be true. But now what I often do is, well, what if the user was marked as subscribed before you even call this? That shouldn't be the case. But
an assertion? That should now be true. But now what I often do is, well, what if the User was marked as subscribed before you even call this? That shouldn't be the case. But if so, I'm not really testing anything here. So let's also do the before and after. To start, the User is not subscribed. But then I do a thing, and then the User is subscribed. This is our test. And you can see all the yellow here because we haven't created that method. Let's give it a run. I'm going to do it from the terminal this time. phpunit on the test directory, and let's turn on colors. OK. Call to undefined method isSubscribed. Fair enough. I should be able to create this automatically, like so. And why don't we slime it for now? We'll say false. So that should solve this test. And in fact, if I ran it, yeah. We don't get green here because we do have one test marked as skipped. But the .env
Implementing Subscription Flag4:43
it for now? We'll say false. So that should solve this test. And in fact, if I ran it, yeah. We don't get green here because we do have one test marked as skipped. But the dot represents a past test. Anyways, if I bring that back and run it again, though, of course it's going to fail. OK. So let's get started. When we call create on Subscription, we need to update the User's local information. So why don't we say user mark as subscribed? How about that? All right. Let's create that method. And again, this is all in memory. So maybe we'll just have something like subscribed by default is false. But when you call this method, we'll set it to true. Now the isSubscribed method is effectively a getter that returns that Boolean. OK. Let's give it a run. And it passes. But now let's imagine that we had worked on this method a little more and we are interacting with that gateway.
Injecting Billing Gateway5:35
that Boolean. OK. Let's give it a run. And it passes. But now let's imagine that we had worked on this method a little more and we are interacting with that gateway. A gateway is kind of a fancy technical term, but it's pretty accurate. It is a gateway often to some kind of billing API. That's the most frequent example or the most ubiquitous example. So if we had some kind of gateway to Stripe, you might say something like $gateway->create. And this is where the actual API calls and the actual interaction with Stripe would take place. All right. Let's try to do that. Of course, it's going to fail, though, because what is a gateway? We don't know. OK. Sounds like subscription has dependency on the gateway. So let's say protected and we'll type this for a very specific reason. And then our constructor will accept that. Or let's see, are we on PHP 8? Yeah, we are. If you want, we could
So let's say protected and we'll type this for a very specific reason. And then our constructor will accept that. Or let's see, are we on php 8? Yeah, we are. If you want, we could just do this inline. I got to say, I'm not sure I love this, but it would allow you to remove all of that. But then your constructor is blank, which to me just feels really weird. I'm sure there was a better way or a better implementation, but maybe not. Nonetheless, on php 8, you can structure it this way if you like. Now, I already have a plain old Gateway class here. But again, this would be your wrapper around your Stripe class. You might even inject your implementation here. And then this, I have a var_dump to represent it. But yeah, this would be maybe where you interact with the Stripe SDK directly. So let's give it a run. Subscription now depends on Gateway, which means our Subscription test.
Using a Dummy Gateway7:12
represent it. But yeah, this would be maybe where you interact with the Stripe SDK directly. So let's give it a run. Subscription now depends on gateway, which means our subscription test needs to accept a gateway here. And now if I give this a run, yes, the test passes. But notice, as part of that test, we see our dump here that represents an actual network call being made. I don't want to make that network call every single time. It sounds like we need to use a stand-in or a dummy. Sounds like this is what needs to change. I want to say, don't use the actual gateway. Use a dummy or a fake version of it. It's not the real thing. Now, there's a number of ways and tools you can go about this. But of course, one option is create a file like this. tests/Gateway/FakeGateway.php. So now we want this to be like the real thing, but not the real thing. So when I say like,
one option is create a file like this. Test, Gateway, let's just call it FakeGateway. So now we want this to be like the real thing, but not the real thing. So when I say like, that means it needs to expose the same API. And this is where an interface might come into play. But to start, you could just say, okay, well, we only have a create method. So let's make sure that our FakeGateway has one as well. This is a dummy though. We don't care what it does. It doesn't need to do anything. It's a stand-in. So let's keep that blank. Now, in my test, I don't want to use a Gateway. I want to use a FakeGateway and try to pass that. But notice the second I changed that, we'll get rid of this, phpStorm started squawking. And it's letting you know, wait a minute, something's going on here. Because we, excuse me, we expect a Gateway instance. But now you're trying to give us
Introducing Gateway Interface9:35
here. Let's see if it works. We run it and it does. Notice the test pass, but we make no HTTP call. And again, just to be crystal clear about this, if we were instead to die here just to show that this method is being hit, it's not. We're no longer using our gateway. We're using a dummy. Okay. But maybe you don't like duck typing. You want to keep your types. Well, in that case, you already surely know what the solution is. In these situations, don't reference a concrete class like gateway, reach for the abstract. And that would be an interface. To do that, you then have to think, well, if the interface is basically a gateway and maybe there's a Stripe implementation and maybe there's a Braintree implementation, gateway is no longer quite right for this. This is actually the Stripe version of your gateway and it implements the contract or the terms for a gateway. So let's go ahead
gateway is no longer quite right for this. This is actually the Stripe version of your gateway and it implements the contract or the terms for a gateway. So let's go ahead and try to create that. It is a GatewayInterface and the interface states that you need to offer a create method there. So now notice on Subscription, we don't care what implementation is used here. It's not relevant to Subscription. It just needs to know, okay, we need some kind of gateway to interact with. And if you want to give us Stripe or you want to give us a fake version or a Braintree, we don't care. Just give us something that can do the job. Normally you will use your StripeGateway class can do the job. But now for our test, we're going to use the DummyGateway version and the DummyGatewayTwo can do the job. And notice, by the way, if I didn't offer that create method, as soon as
But now for our test, we're going to use the dummy version and the dummy version two can do the job. And notice, by the way, if I didn't offer that create method, as soon as you implement the contract, NIDE is going to let you know, hey, make sure you include these methods. These are the terms and conditions of the contract, the TOC. All right. So if I give it a run again, it's all going to work, but now we are using types. So it's entirely up to you which option you prefer and you might reach for both depending upon the project. The key thing to understand for this lesson is that a dummy is a stand-in for the real thing. When, for whatever reason, a test doesn't need to interact with a particular thing or a particular object, reach for a dummy, which is a substitute for the real thing.
