[18/38] - Defensive Counter Measures
37K views
Feb 14, 2023
Full code samples and write up -https://www.codereviewvideos.com/course/let-s-build-a-wallpaper-website-in-symfony-3/video/defensive-counter-measures Learn how to use PhpSpec to implement a defensive coding practice. We want to check what doesn't happen, just as much as what does :)
View Video Transcript
0:00
Hi, I'm Chris from CoddreviewVios.com and in this video we're continuing on with our test-driven implementation of our wallpaper upload listener
0:12
Towards the end of the previous video, we'd started writing the tests or the specification of how we expect our wallpaper upload listener service to work
0:19
We'd covered how we expect to react to doctrine's pre-persist and pre-update events, and as part of that process, we'll need to move the uploaded file from where a PHP is currently
0:29
storing it in its temporary location into a location on disk that we control, which in our case
0:35
is going to be the web images directory. We've already written our file mover service, which we're
0:40
going to make use of inside our wallpaper upload listener. Let's take a look at where we're at
0:44
currently with our tests. So you can see we've got a failure which is expected because inside our
0:50
file move and move should have been called with these two arguments and it wasn't called
0:55
It's not been called because we have no logic inside our pre-persist method. Now one interesting
1:00
thing that we're going to cover in a few videos time is where this life cycle event args object
1:05
is coming from. And again, for our pre-update function, where the pre-update event args is coming
1:10
from. And I'll give you a heads up on this. It's to do with tagging. So when we come to define
1:15
our wallpaper upload listener service inside our services.comal file, we'll have to tag this service
1:21
in a certain way so that our wallpaper upload listeners pre-passist and pre-update functions are called
1:26
at the appropriate times. However, there's a sort of got-jure. here, and I think I mentioned this a few videos back, these pre-persist and pre-update functions
1:35
are going to be called for every entity in our system In our case we have a wallpaper entity and we have a category entity So whenever a category entity is created and it about to be persisted this pre function is going to be called
1:48
Likewise, when we have our wallpaper entity, it's also going to be called when we do our persist call
1:53
Now, we want it to be called for wallpapers, but we don't want it to be called for categories or anything else
1:59
So we need to get a little bit defensive. So before we can really consider things such as file moving and whatnot
2:05
it would make more sense to write a test here first to ensure that we're only interested in
2:10
wallpapers. So let's add in that test. So that test name is fairly horrible, but at this point
2:16
in our system design or development, it's been fairly explicit about what we're trying to do
2:21
So by way of a few comments, I'll put in here what we're trying to do
2:27
So really to make our testing as simple as possible, what I'm going to do is say this, pre-persist
2:33
and we'll need to call it with those. event tags. So I'll need to pop them in to the function signature to make sure that we get a
2:40
collaborator that represent our event args. We don't need to do anything with them at this point
2:45
We're saying when pre-passist is called, then we should return false. Okay, so let's explain our
2:51
intention here a little bit further. We're saying we're going to call pre- persist with some event
2:55
args, which is what we have inside our implementation. Then we're going to look inside those event
3:00
args and check if the entity is an instance of wallpaper and if it's not, we're going to return
3:04
false. However, if it is, then we're going to return true. So let's just run our tests first to see what happens
3:11
It's always good to see your tests fail before you try and make them pass. So this is the test that we're
3:15
interested in saying we expecting false but we got null Now we can immediately make this pass by simply returning false That doesn really help us in any way We completely sort of cheated there
3:27
So let's just get rid of that. However, the implementation here is pretty straightforward, honestly
3:31
We can just say if event tags, get entity, instance of wallpaper
3:37
and we'll say if that is equal to false, then simply return false
3:42
Otherwise, we'll just return true. In fact, we can just get rid of that comment there
3:46
semicolon and we're good to go. Let's test this and we have a passing test and that's a little bit
3:51
strange because event tags get entity isn't actually returning anything as far as we're aware
3:57
We haven't told it to. It's not the real implementation of our lifecycle event tags. It's actually
4:02
a PHP spec collaborator and we're not told that collaborator what to do when get entity is called
4:08
So if we don't tell it what to do by default, PHP spec is just going to return null. And we can
4:13
check this ourselves by dumping this. And testing this, and you can see, we're just getting a null
4:22
Well, null is not an instance of wallpaper, which is why we return false
4:26
And again, that's why our test passes. Now, you may not be comfortable with that
4:29
You may prefer your tests to be a little bit more explicit. So what we can do is we can say event tags, get entity, will return
4:37
and this is just sort of where we can completely fake what value gets returned, or just override what value gets returned as such
4:44
And we can say return as a category. And that's a new instance of the category
4:48
It's not a collaborator. And now our test should still pass, which it does
4:53
It's the other one that's fail in there. And this time the intention of our test is a little bit more explicit Now thinking ahead a little bit we know that if we are going to be working with a wallpaper so if we not returned false and we got to this point then we going to want to do something with that file mover which in our case is going to be moving a file So even though
5:11
we're sort of testing the unhappy path here, it might be worth checking that the file mover was not
5:16
actually called, because we may end up with a few of these sort of guard statements, and they may
5:21
occur at various points throughout our prepersist function. So you can sort of think of this as like an
5:26
insurance policy in a way. We can convince ourselves that this definitely didn't get called in any
5:31
capacity. So say this file mover move and we'll say should not have been called. But we've hit on an
5:36
issue here because we don't know what these arguments will actually be. We've not set them up in any way
5:41
but we know that when the file mover is called for real, it should have two arguments here. The
5:46
temporary current file path and the new file path that we want it to be moot to. So a really useful
5:51
technique here is to use the underlying prophecy library, which comes with PHP spec, to use the
5:57
argument any, which will literally allow us to represent any variable or any argument being passed
6:04
into this function. There might be a string, an integer, another object, but we don't care
6:08
because in our case, this should never happen anyway. So that should not be called, so this
6:12
test should still be passing. Okay, so that's quite a lot of the setup out of the way
6:16
but from this point on, things get even trickier, at least when it comes to testing. So what we're going to do
6:21
is we're going to implement the file upload process without any tests
6:25
We're going to see how it works. Then we're going to completely revert our change
6:29
come back to this point, and re-implement our file upload process but using PHP spec
6:35
And that's what we're going to get on to in the very next video
#File Sharing & Hosting