Workshops by JMac - Using Mockery for Test Objects

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
and hopefully that might also help with some of that CPU so today we are gonna be talking about mockery and mockery is a testing it's one of the testing frameworks it specializes in creating test objects we'll talk a little bit more about why we want to create test objects here in a second but let me first kind of back up and talk about this project setup so let's dump out these remotes so if you go out on github you can see under Jace McCreary start testing PHP I have a sample project we use this last time but basically inside of there I've also created several branches and what I've done is check out this mockery base and what that's done is kind of given me not only the composer set up with all the dependencies that we want but also some files that we'll use to kind of start testing and start kind of exploring with mockery so this is the same project we used last time we started I think on master with the collection base and we were getting familiar with phpunit so this time like I said we kind of jump out to this mockery base and then each one has a base and then there's also a final so if you take off the base suffix when we're done today hopefully it'll look very much like the mockery final result so after the fact if you guys want to take a look at this repo and just kind of look at the differences between the files and so forth there's several things in here I think stuff is just a local one where I have a script that I use because I know I'm gonna give this in conference workshops and so forth so it's actually like a four-hour workshop so I've broken this up into three or four different of these kind of online workshops so anyways let's go ahead and get started I've already run a composer install so everything should be fine there I'm running a PHP unit which again we talked about last time and then mockery again mockery is something that allows you to create test objects and the reason we would do this as basically while we're testing our system you know obviously there's going to be collaboration between objects within the system and in order to test and kind of facilitate testing sometimes we may want to kind of control the way those objects interact with each other to test something in isolation there's a lot of different words that people use for this so let me just let's do this actually let me just run and get status and let me get us like a little workspace bit here so let me run I get clean ft we'll just clean everything out I'm gonna jump back to phpstorm and let's make this just a little bit larger for us okay cool so basically let me jump in this readme file I'm gonna get rid of this so there's a lot of different what people call test objects and a common term for that is basically double so they'll call them a test double and what that means is it's kind of a stand and right for these real objects right we have these real objects that that are obviously what are used when our application truly runs in production but when we run under the test environment we might want to again double these out using these test objects and that's what mockery allows us to do there's a lot of different kinds of test objects there's you know fakes there's dummy objects there's mock objects hence the name mockery some people will actually go so far as to kind of be specific and talk about stubs and there's more there there's a lot more here people can be really trying to think of the right word maybe pedantic about these there's a great post by Martin Fowler who kind of breaks these down and into kind of the differences between them I recently kind of paraphrase that and one of my dev two articles so those that have seen those or found this workshop through there you might have already read this but I'll share it in slack at the end of this workshop the point really here is that there are some nuances about how these things work differently so for example a dummy object my not be faked it might not be something that you build with mockery for example it might just be a true like physical object in your system that you have you know set up or something that you use in its place Knoll can even be a dummy test object right you can just pass it no because it doesn't really matter for the purpose of your test so you just kind of dummy it so again there's a lot of different examples of these what mockery focuses on actually I remembered one of the other ones here sorry it's gonna be important so there's also something called spies so mockery really excels in this mock and spy section so that's what we're gonna kind of focus on today so what I want to do is let's let's get away from some of this terminology because I think the final point I was always going to make is that when you're starting out the nuances between these don't really matter they might over time and and again some people might be very specific about the way in which they want to use certain test objects while they're testing their system but it's not something that I think is really critical especially in the beginning so I'm not gonna focus too much on the difference here although I will demonstrate for the purposes of mockery what it kind of means for the differences between mock and spy so I'm gonna jump in my test directory here and again your project setup doesn't necessarily have to be the same as this but what I'm gonna do is just let's just make a mock whoops a mockery test and I'm gonna say okay this is just a mockery test dot PHP and what I'm gonna do is this is just gonna be a class and this is just a PHP unit test basically so I'm gonna call it mockery test I'm gonna extend extend PHP whoops that's the old way I'm gonna extend a test case here and let's auto import that and correctly and let's import the class that is a PHP unit framework test case so first thing I want to point out here is that mockery does not have like it's not a full-on testing framework it really focuses on those test objects so we're gonna be using PHP units still to write all of our tests but we're going to be using mockery to potentially create you know some of these test objects to help us test our different classes so I'm going to get to that in a second but to start out here let's basically just make kind of a base mock let's let's do this hist base mock and just to kind of get a feel for the behavior of what mockeries doing so I'm gonna do is just do use mockery to make a simple mock and I'm not gonna set anything else up on here right so that's it's that simple you basically just create a mock object and what this is is a just Fonua nothing special about it straight up mockery mock it's not mocking any kind of class it's not doing anything it's really just your your super generic mock object and what I can do with mock is I can say mock should receive and let's say let's just make up a totally you know basic method name here so let's just say should receive a call to something right and I'm gonna have it and return five right so what this sets up in this case is it's saying okay I have this mock object that objects gonna receive a call to something and when it does I want it to return five and let's go ahead and make just a basic PHP unit of some assertion here so let's assert same and again from last time I like to use assert same instead of assert equals just to be super explicit about it so let's say that when I call mock something that I should get five back right so very contrived test here but what we want to demonstrate is that I've created an object out of thin air and I've kind of set it up right I've stubbed it out if you will to be that whenever I call something it's gonna return five and then I'm basically just making this assertion that hey when I actually call this mock object it's gonna give me back five but I call something on the mock object let's save that I'm gonna jump back over here vendor Ben PHP unit and everything passes there we go so let's let's actually change this cuz like I said anytime I had shot something on the first time I'm always a bit skeptic so let's change that to six and we get a failure failed asserting that five is Anna Nicole six so there's a couple interesting things that are happening here let's put this back so let me just go over this a little bit more so mockery gives us a mock object you can think of it like a standard class right and what it allows us to do is stub things out on it so there's one of those terminologies right we can basically stub out any method that we want with it and we can tell it to do something else so even though in the real world we might have an object that our class uses and it calls this method something I can tell it to return five you know instead of whatever it would really do right I can just completely have this stand-in test object right this mock object and I can control what it's doing to allow me to better test the class that way I'm not testing you know this entire system I'm still able to kind of test in isolation right I'm still able to write somewhat of a unit test even though there's collaboration between objects that's happening I'm able to kind of focus down and test that one thing in isolation because I've controlled all the other things around it and these test objects allow me to do that there's a few more nuances before we get into some real-world code so let's do one more thing here let's just say that I want to try to call something else right and I haven't stubbed this out right I haven't set it up to receive anything I've only set up something not the something else method so let's save this and run that so we see that we don't necessarily get a failure here we get an exception and let me turn on colors cuz that's just lame without it there we go so okay so we see that we get an exception here the exception is mockery unfortunately mockery is not very good about its names for mock objects but we see that method something else does not exist on this mock object so by default mockeries mocks if you don't stub out a method and it's called it's actually gonna throw an exception so it's not going to allow you to do that so it's very strict if you will about if you create this test object you need to set up everything about it for it to not complain to not throw an exception so let's just say something else and in this case let's return null just for giggles and down here I'll say assert null I can get my casing right there there we go all right so I've set it up now let's see if this exception goes away and everything passes great one test tube assertion so everything's fine there's a couple other ways that you can do that but the again the important thing here is that by default you have to stub everything out with a mock you have to set it up everywhere so you have to say these should receive should receive so if you have a mock that's doing if you're gonna use a mock and it's gonna do a lot of things there's some other properties that you can give these mocks so for example you can say something like should ignore missing here and what that means is that if you don't have something stubbed out by default it's gonna automatically return null so let's run this again and everything should still pass but notice that I don't have something else but down here it automatically returns null because I told it to ignore missing now a lot of this is in the documentation but mockeries documentation isn't like some other documentation where you can kind of see each one of the methods listed out you're welcome to take a look at it but it's it's a bit more like a book and it's it's not a complete book there's some chapters missing here and there if you will so but just to kind of drive this point home let's let's just say that we you know wanted something else to be - we should get a failure here not the exception that we saw before but a true failure from phpunit saying failed asserting that no is identical with two and again that's because should ignore missing by default won't forcibly throw those exceptions it's just gonna throw knowles instead anytime you call something that you haven't stubbed out and you've set the mock-up to ignore missing it'll basically just return null everywhere so all right let's do one more kind of learning it here and then we will move on to some real-world code so let's test a class mock and what I want to demonstrate here is that what I can create is a mock let's mock this task object right I have this pretty basic just model if you will of tasks and we'll talk about this in a second but basically what this what the system is doing is you can think of it like a task list pretty simple straightforward and everyday example right and so the models here are basically each task has a note right it's just the content of what that task is there's nothing else there's no other really properties here there's no ID or completed date or anything like that yet we're gonna push that out we're going to drive that out as we keep testing maybe not today but in the future so right now though what I want to do is just demonstrate that I can use mockery to basically mock a class so let's say mockery mock and what I'm gonna do here is give it the task class name and I just follow this convention cuz you could you could put in a name of the class there and I'll actually try to auto resolve for you but I like using this class property because it gives me some kind of runtime checks to make sure that that you know class didn't change its name or move anything else move around and in some other way so again I could have said as a string here you know tasks or even the full namespace you know start testing your PHP models tasks I could have said a few different things here but instead of doing all that I like to leverage PHP s class constant here class static class property I guess is more accurately what that is when I create this mock instead of just being this dummy mock it's actually gonna be a mock of this task so technically it's gonna extend the task class and then throw in some of the additional mockery goodies there to allow us to do things so in this case I can say should receive let's do that get note that get er we're gonna stub out that get er and say and return whatever and down here we can do this cert same whatever whoops when we call mock was this get note right all right let's go ahead and run our test here and good everything passes so again very closed-loop demonstration here but the point is is that we can you know extend this class which is going to not only help us out and kind of give us a bit of a template with all the different methods that are possibly available but it's also going to allow us to do things in our code with let's say for example we're using type hinting right and we expect that you know something's going to receive or return a class well by mocking it in this fashion we're now able to pass those tests right because again underneath the covers mockery is really just extending this class that we tell it and adding on some additional methods and kind of magic happening to allow it to intercept and and kind of change these different calls right but it would help with things like type ending or any time you're doing instance of or any kind of type checking that's happening within your system so I like to try to use some kind of you know I guess an instance mock if you will so in this case this is a mock of the task class I like to use these as much as possible it's it's very rare that you know I'll make just kind of this empty or generic mock because a lot of the times when I'm doing something like this it's possible that I probably just could have used some other type of dummy object meaning some other kind of like real-world object whether it be just some standard class that I threw some properties on or whatever again I like to try to name them as much as possible okay so let's see some of this in the real world right so let's make in fact let's just delete this file and let's make a new test repository class so what I'm going to do is task repository test don't name the PHP and let's do class task repository test I think I was saying that backwards extends test case and again that's a PHP unit test case all right cool so what we're gonna test is this task repository and this looks a little bit more like maybe some real-world code that you might have right you have some kind of data access object here in this case kind of the repository pattern where you're basically having using it to kind of manage all of your database connectivity and so forth so instead of kind of clearing the data directly in line with your code you're gonna be asking this repository to say hey give me all of the tasks create a task we can imagine that there would be other you know crud like operations in here or other kind of helper methods to maybe get you recent tasks or completed tasks or whatever so there's a nice way to abstract all of the you know all of that kind of SQL out of here so but the important piece in this class is this piece here and so let's talk a little bit about I guess how test objects can be helpful or say some requirements for them to be helpful right a lot of times that requirement is basically simply put as dependency injection if you're not familiar with the pendency injection basically that means that instead of for example here creating a new MySQL I object inline right I'm instead passing it into the constructor right so I'm creating a task repository and I'm saying that okay in order for the task repository to do its job it depends on a database connection and so you have to pass that database connection in as well the nice thing about this instead of doing it in line as we as I've kind of written here is it now gives me a hook into this class to be able to kind of test that collaboration right and that's the whole point of this is that in order for us to even be able to use a test object we need a way to kind of pass that test object into our system a dependency injection or even Method injection so for example down here on create note we would have the ability to pass in some kind of note data right to manipulate it so any kind of injection right whether it be at the class level or at a method level is going to allow us those hooks that we need to potentially pass in some kind of test object so if you have code that you know looks a bit like this you're gonna have to do some pre refactoring to where you are now changing that to kind of be passed in in a different way to be passed in to the actual object so you can then use it now this this creates some other problems right dependency injections it's not really necessarily just that simple right you can't just go replace those news because somewhere someone has to create that database connection right but that's something if you have some legacy code that you could do and just cut some kind of configuration script you can make some kind of provider that kind of gives you those back in a way right or you can look into you know I guess different kind of containers out there there's a lot of different ones in the PHP community obviously if you're using any kind of framework they probably have some kind of inversion of control or ioc container that kind of helps you map all these and kind of figures all that out for you definitely a talk for a different time but the simplest thing to do today is basically just to change the constructor to accept a MySQL connection so now that we have this here let's kind of start messing around with this a little bit more so let's say that I want to test this all method given that I have this database connection I now can make some assertions on all to ensure that you know these different paths are working correctly and as far as today goes again I'm really kind of testing more at what people would call the unit testing level so I'm really I'm really wanting to test kind of that each one of these things is happening right I'm not necessarily necessarily testing more of an integration test if you will where I call all and I get everything back that was in some kind of test database like it's truly behaving instead again we're gonna be at a much more granular level here where we're actually testing that the codes kind of doing what we expect it to do right so for example we want to test that it's calling a query and getting them in you know descending order right so we kind of want to make sure that this query is the query that that is run some people aren't the biggest fan of testing kind of in this way again when you're starting out I think it's good to kind of get the feel for it and see what works for you and see what makes sense as far as your codebase goes so today let's just go ahead and test this you know kind of again line by line if you will so I'm gonna split the screen here and make life a little easier to look at both sides and let's um bring our test back up okay so let's test all and let's kind of test just starting out here so I see a couple different paths anytime I'm looking at at you know a new class that I want to test or if I'm driving out especially if I'm testing legacy code I kind of want to take a look at the different paths here so immediately the very first path I see is that okay this query is run and somehow it returns false and so I would expect to get back an empty array so that's kind of path one there so let's test that so I'm gonna say public function test all returns empty array when a query result is false so again very specific test here just for that very specific path so the first thing we want to do is we need to make a task repository right so we can simply say new task repository and the question now is we need to pass in a MySQL this MySQL database connection right so what we can do is we can actually make a fake one here this is this is exactly what we want to do right this is the whole point of mockeries so let's go ahead and set up that fake one real fast so let's just say something like DB connection is equal to mockery mock and I am gonna attempt to mock the MySQL class again if if you can try it so let's definitely attempt to do that and then I'm gonna pass that into this DB connection class right and this is going to be my subject this is what I'm testing right subjects does a nice shorthand inside of tests that's really common to talk about the subject under test or the suit so anytime I'm I'm inside of a test where I have multiple test cases instead of calling this like task repository I like to call it subject cuz that just lets me know hey this is the thing I'm testing this is my test subject right that's where you get the name from so we're gonna pass this in and now let's set up some assertions on this so we've got our new task repository it's subject effectively we want to call subject all not ass and then let's actually make an assertion on that right I'm just gonna be a little pedantic right now so let's say this is my actual result right and then I want to assert let's assert same and I basically am expecting an empty array back and this is what I actually got back will convince some of this code here in a second so I'm just jumping ahead a little bit and let's go ahead and run this and see what kind of explosions happen so let's work through it together here okay cool so first of all class start testing PHP repositories tasks repository not found okay so seems like there might be a little bit of an issue with my auto load so let's fix that real quick Auto load all right let's see if that helps it still does not okay what am I missing here let's slow down okay that seems correct let me just make sure I am Auto loading that in my so testsuite name start testing PHP let's make sure all the auto loads okay there it is I do not have my auto load set up so let's set that up real fast auto load and then this is a PSR woops psr-4 and whoops let's not forget my colons there and then this should basically be what do we have here underneath source is my start testing PHP right okay so I'm pretty sure it is the name of your namespace I don't write this very often because it's normally just kind of our t in the project and then source I'm gonna assume that's okay I might need those don't remember I don't think so though let's leave those off this is where having slack open would have helped let's run dump auto load oh there you go it does into the namespace separator thank you composer all right let's try that again a non-empty PS our prefix must end non-empty PS are prefixed Oh wrong way there we go alright cool let's see if all that's better in the world ah cool great that's much better okay so now that we got that fixed let's start over here and look at this error okay cool so this is our test received query but no expectations were specified great so we saw this before right with the mock that we have if we don't set something up right if we don't set up query to return what we would expect then mockery is actually gonna throw an exception in this case we don't even get down to our assertion failure so let's close composer and PHP unit there and get our screens back the way we like so let's get back to mockery now we have set up our PHP unit test correctly but with mockery we need to stub out our database connection and tell it what we want it to do right so we should receive query and for what we've learned so far let's just say and return false right because that's going to set up this path the way we want and get us in a path where we would potentially return an empty array all right great we pass okay so it's pretty good but let's learn a little bit more about mockery so this is nice and this works but again if we're if we're testing at this level of granularity right we really could be a little more strict about what we're testing and let's also for the purposes of learning you know learn a little bit more about learn a little bit more about mockery so let's do some stuff here right let's expect some arguments right because we're gonna expect that we get called with a very specific query like this is important to us right it's important that obviously we're selecting just note we're not selecting star for example maybe that's maybe that's kind of a convention that you use as a team you know to save on you kind of fine-tune your queries and then let's also say oh you know it's kind of business critical that the order by created like you're your newest ones are first right so let's use this with args and we're gonna pass in whoops phpstorm being a little overly helpful there and we're gonna pass in that right so we're gonna make sure hey look not only did you receive query but you also received it with this argument and then you're gonna return false let's run this again um with args with an invalid argument um you know what I think it's just with maybe or with aargh there's two different versions and yeah there's two different versions in mockery so with args I think has to be an array or you can do with and there comma-separated with the different arguments so I think whiff is a little bit more common to use I'm not sure why we're getting an odd highlighting in phpstorm though but anyways everything's good there we're passing so let's push on so that's that's a real simple test there so let's make another test let's test kind of like this other path here but in this case number O's we're gonna kind of move down the line so false is good it returns let's move down the line here and make sure that when we do return some kind of object its numrows as equal to zero then we would also get an empty right back so let's say public function test all returns empty whoops empty array when query has no results right all right so we're gonna do basically the same thing here so I'm just gonna copy paste a little bit of this let's go ahead and setup our subject whoa let's set up our subject great and then we're also going to set this up to receive everything it received a minute ago but in this case we want to return something else right let's leave that alone for just a second and then let's copy this actual bit here so let's for now I'm just gonna return null right and let's run this and watch it explode great okay so trying to get property of an on object so we can see where this failed line 21 so we can look over here line 21 so it didn't return false right so it's not strictly equal to false so it's gonna go over here and it's gonna say null Eero numrows is equal to zero so obviously that's a little bit of a problem so we need to return an object so instead of returning false or in this case while we were just stopping things out no let's return something here and what I'm gonna return here I'm gonna demonstrate using a dummy and in this case I'm just kind of being pedantic again about that terminology but just to kind of emphasize this here we could make another mock object right and we could create a property on that mock object when it called numrows for example to return zero again I don't like using mockery to necessarily make these generic objects like these generic test objects I want to instead to kind of set them up in a way I want to instead try to push myself to see if there's something else I can do so in this case if I really only care about numrows like this one property on here I can really just make just a standard class object right so I can just say new standard class right and then I can just say dummy numrows right automatically make with the dynamic nature of PHP I can automatically make this property on here this public property immediately and I can say okay look you're number I was a zero return that dummy object when you're called with this and we should now not be getting these null exceptions we should actually have an object here just some dummy object that has a number of property on it zero so again not necessarily using mockery in this example we'll use mockery again here in a minute but for now I'm not going to mess with it I'm just gonna use this dummy object so let's see how far this gets us great everything passes let's just change this to one real fast again just to see that we moved a little farther okay great so we see call to undefined methods standard class fetch Assoc and that's basically because this failed we moved down here and we actually got in a situation where it was trying to call on line 26 it was actually got farther in the code and tried to call this fetcher so obviously we don't have setup yet but that's a good indicator that this test setup is actually gonna help us kind of keep moving forward to the next so let's do that let's push on just to a few more tests then I'll open it up to questions here so let's do test all returns tasks when query has results right so this is kind of a little bit better of a test here so we could keep pushing on this standard class so we saw that we have this dummy we can set this up to be two for example and then obviously that's gonna push on and give us kind of this same error in our code that we saw a second ago right it's gonna get to line 26 and it's gonna attempt to call this fetch Assoc but there is no fetch Assoc on this dummy object now again we could kind of do some very dynamic stuff here in in PHP to kind of make it work we could say something like dummy you know fetch Assoc and we could assign some closure to it that starts to get pretty weird right so let's go back to using a a mock so instead here let's say something like result equals mockery mock and what I'm going to do is really what query returns is a MySQL result object right so here it is here so we could try to do this and let's just Toya round with this for a second and let's say result equals what's result numrows equals two all right let's see if this gets us farther okay so there's a really interesting thing that happens here it says cannot write property where this comes from is actually underneath the hood in the MySQL I result object the num rows property is read-only now that's not something that's exposed to us in PHP in like a user land we can't necessarily mark a property as read-only but underneath the inside of like the PHP engine they can actually do that and because of that we can't necessarily use this MySQL result class directly and then overwrite that numrows property again that's something that's kind of generated automatically so we're not able to mess with it it's a very interesting error and pretty specific to this code sample but I want to bring it up for two reasons one as much as I do like using this class directly there are gonna be some times especially in the case of when you're trying to mock out core like native PHP objects that you might not have let's call it visibility right into certain properties you might not be able to really mock those out so when that happens and kind of leading me to my second point you have a couple of different choices you could over here do something instead of num rows you could you know set it up to where you call a method right I don't think it's count but there might be like a row count there's a method that represents num rows inside of this result I don't remember it off top my head but you could modify your code to do that I don't necessarily like going down the road where I'm kind of having to write code in a certain way to test it a certain way right I think that starts really getting down that road where um you know you hear a lot of people talk about oh well you know when you write your code that way or when you're writing your tests that way they're they're two coupled right you're kind of just writing a test to test the implementation line by line by line and I think in that case you know you're definitely doing that right you're definitely having to write code in a very certain way for it to be tested a very certain way I think that's dangerous so what we're gonna do is just kind of go back to just creating a generic mock object we're not necessarily going to try to necessarily mock out the MySQL results class because we're gonna start running into these like low-level core problems and and this is one that I noticed before there are others but definitely again continue to try to use them if you can but if you start running into these things we're like you're just not able to step out methods and you're getting some very interesting errors that might not make a whole lot of sense you're not getting like the mockery or phpunit level errors you're getting these interesting like cannot write property that seems a little bit strange right so let's just drop that instance mock that we were creating a second ago and just go back to just a generic mock so we can this is just an object like anything else so we can just set that number as property immediately and we should be back to where we can't oh it's not dumb anymore sorry it is result and okay good so fetch Assoc doesn't exist on this mock object great so we're back to where we started just a slightly different exception now so let's set up back using mock or mockery let's set up fetch Assoc right and fetch Assoc it's gonna receive there's no arguments so let's return and will notice that return can actually take an array so it's a little bit interesting so we can actually set something up if it's gonna be called multiple times to basically be like call one call to call three right and it's just going to keep returning those until it effectively runs out so if we think about the way that fetch Assoc you know kind of behaves in the real world is that basically it's gonna return a result and then it's going to return finally like null or false or whatever that last time so that while loop dumps out so let's just have it it's gonna give us one object back to object back and then it's gonna return null let's see how far this gets us okay so great so we did get farther we got down to free so let's let's keep pushing on here so what we're also going to do is now we're not going to assert that we get back an empty task list anymore right so we've gotten down to this free part let's make sure that we actually get some tests back though right because it we looped over two things we should be getting two tasks back so let's do this let's say actual we should get back a note right so let's say actual zero so this first task that we get back should be something like task 1 and the second test we get back is task 2 right and we could also just just for giggles we could say something like assert can't and we could say actual whoops we expect its count to be 2 right all right cool so we should get an array back right of tasks where we say two of them are in there and then whoops sorry this should also kind of imply that it's a task object so we can call get note on that we should remember that from earlier that model that we had built and that should occur right okay we're still gonna run into this free right because we're back in that scenario where mockery doesn't allow us to call anything if we haven't stubbed it out right so if we haven't set it up to do anything it's not gonna allow us to to kind of mock or tonight allow us to keep going so for right now I'm gonna come back to that in a second but just to push us on to these assertions here I'm just going to comment out that line of code because that is the last piece I want to cover okay great so now we got null is identical to task one so you might have thought okay well that and return was a little interesting I just put one two and null so let's talk about this a little bit more what we need to return here basically is fetch Assoc returns an associative array right and because of that we need to set it up to not necessarily return one and two we need to return some more interesting bits here right so what we need to return is a note associative array right for those tasks so let's say this is task 1 and this is going to be a note and we've worked with MySQL you know data data records enough where we can basically just kind of reason about the way this should look so again on the left the associative array is gonna be the column name and then on the right it's gonna be the value so in this case note is our column name and then the value should basically just match what we've kind of asserted down here right so let's run this awesome everything passes cool so a little bit of setup here and this required kind of knowing the way the system was going to behave and again this gets to that point that I was mentioning a couple times now that you know we're really kind of testing in a way line by line here right we have to kind of set up these mocks and in very specific ways to behave as they would in the real world so it requires us knowing you know some things about that code oops all right I feel like a copy-paste it's something we are in there let me make sure okay good all right and the last thing I want to do is the second ago I commented out this free part right well freeze not really doing anything but maybe it's important to us maybe we have a really really large task list right maybe we're Basecamp and it's important that when we call this task repository all that we're freeing that result up from like a memory perspective so again while this might seem like a rather trivial line it's something that you know probably matters in this case and we want to make sure that we call it so it doesn't make sense to say should receive on it necessarily what makes sense is to verify that it was called because there's no really output result of this it's not like query where we return something and use it in our code or fetch Assoc where we return something and use it in the code it's just kind of this freestanding independent line of code that we need to make sure something happens on it so what we want to do here is after we make our invocation we can actually verify something on that event mock actually happened so I can say result oops should have whoops should have received free and we can even say something like once so make sure that free got called that you know at some point we don't really we can't really assert that it got called whether it was here or up here you know maybe it was called the very top but reliably the code necessarily wouldn't work if you called free much higher so we can trust again then it's it's it's working and behaving correctly so long as you called free one time and again doesn't make sense to call it twice or whatever so sometimes tacking on those extra little configuration bits are nice and they also demonstrate the additional things that they can do okay cool so free does not exist on this mock object we're still getting that same error this is where mockery gets a little bit silly but we also have to make sure that in this case we're not really setting up a mock we're setting up a spy and the difference between a mock and a spy on the surface is really that you can do this and I say on the surface because really a mock object can do the same thing we would just have to set it up to ignore missing but let me do it straight let me hold that in a thought for a second I'll demonstrate that here a second so instead of making a mock object directly we're gonna make a spy which behaves very similar the way a mock does but also allows us to make these verifications on it so let's run this make sure everything works and it does great we got called once again I might be able to change this twice and we should see an error here right so it should have been called exactly two times but only called one times so just to kind of demonstrate that extra little bit there so underneath the covers spy is really a mock that should ignore missing and let me just demonstrate that so I went back to a mock but I tacked on that sugar missing and I'm still green but a spy does pretty much the exact same thing and if we look under the covers of spy I just command click through to the actual implementation in the code if we see spy it really just makes a mock and calls should ignore missing on it so it's just a shorthand really they're both the exact same thing as far as the mock object that's created they behave in the same way some people like to see this spy here because it just communicates that oh I'm gonna make some verifications that method calls actually happened but I'm still able to set it up and do things with it just as I would any other MOC so it's a little bit interesting though because a spy won't necessarily complain when you don't implement you know I should receive on it right so I'm not gonna move on to create because I think there was a lot that was covered here so what I'd rather do is open up the questions just to make sure we've kind of covered it enough with the basics and we'll go into a lot more detail and in kind of a more robust system next time so let me fire up slack here which I think last time took quite a minute to fire up with what the cpu burnin so let's see what's happening well that's firing up let me make sure that everything is still running correctly great all right cool again while this is coming up you guys if you want to jump over to the questions channel you can start posting any of your questions in there again this might take a second or two I know last time it took like 45 seconds to fire up seems like everything's still pushing so that's good alright slowly but surely getting there great ok cool so it looks like there were a few questions in here so yeah if you want to jump over to the questions channel I know I saw some things in workshop I'll try to pull them over but yeah if anybody is still on the live stream and wants to ask some live questions I'll be glad to use the last 15 minutes or so here to try to answer some of them so thanks for those that watched live while we're waiting on some questions to come in I see a few typers so while you're doing that again I'll just remind everybody of the repo or well it's probably better just let's just open up chrome here and share it out the right way that way you guys can see all the links all right so let's go to github.com Jason McCreary and then start - testing - PHP I think that was it cool so I'll share this out to everybody in slack let's see please post in questions all right cool so um repo from whoops so this is the repo from the workshop cool and then there was I also mentioned that mockery bit let me jump out - dev - I posted this I think last week or or so whoops that's not it what are the users I think there's like a min here somewhere there I am yeah this is the other post that I mentioned the other day is this kind of joke here fake it by stubbing the mock dummy just talking about all of the different nomenclature and just kind of the nuances between these different dummies fake stub spies and again you heard me using them a lot during the workshop at the end they don't really matter and I also kind of built this fancy little test double helper inside of mockery so instead of having to kind of remember all the time they should receive and like you know to ignore missing and so forth I kind of just made this simpler in my opinion double class it's just kind of a top-level helper methods I'll share that out as well okay so that's that post which also has a link to the same bit so let me jump back over to questions here okay what happens in the red green refactor loop when mockery in your test has been so tightly bound to the implementation yes that's an excellent point so what we're talking about here is or I guess what's being asked here is okay for example let's say that this loop that I was doing down here I go over here and somehow during kind of a refactor of the code all my tests are passing everything's good but I noticed later in development that like oh this could be you know done in a different way and I start changing this up and in some way right I start changing up like result you know something like not result and things like that I'm trying to think of a good example here and that I can kind of mess with maybe I don't fetch a sauce anymore right maybe I just because I know there's just one bit here maybe I do fetch row because I later find out that that's faster right I'm not fetching an associative array anymore I'm just fetching a row with with one objects this is this is a good example I think off the top my head to demonstrate that now when I go and run this test my test is going to fail right because it failed asserting that the actual size zero matches expected size to well the point that's being made by the question is that because I again I've kind of granularly tested I've kind of tested very specifically that I was calling fetch Essos and it gave me back this very specifically formatted array that if I ever wanted to go in and kind of change this up with a simple refactor my test is going to break now I think that's an excellent point and I think that's where you have to kind of try to find a balance of what makes sense in your code right if you're finding that you're having to change tests a lot because this methods changing constantly it may be better to write some kind of integration test write some kind of test that's not super granular at this level again I've done this to demonstrate kind of the purpose of mockery and how we used it on a couple different ways and how we can use it at a real granular level but yeah for sure if you're in a place where your tests are super tightly coupled to a very very specific implementation I think that's a really common point that people make kind of against writing you know these types of tests right not necessarily writing unit tests but but writing this kind of specific line by line kind of mocking and I think the challenge is again to maybe back that test off a little bit to where maybe it's not testing this so so specifically right maybe it's doing something else maybe you have some kind of helper or something that you're able to to mock in a uniform way but that's the challenge right your tests are telling you something here that whole process if nothing else is telling you hey not only that your tests are super tightly coupled but how could the code change where you're not necessarily having to jump to the conclusion of writing an integration test you don't have to abandon this unit test and go straight to integration there's some gray in between right where you might be able to say oh you know maybe this while loop here could be better served in another way so definitely push yourself you know don't just abandon you know writing tests you know writing unit tests for example and jumping straight to some kind of integration or Indian test as people call them so it's it's a very good point though right like it I think that is a challenge but in the real world you might not be writing this again this level of granularity maybe you are but again this is a very low level piece of the system and maybe that's okay maybe it's okay that it's this this tightly coupled right uh will I be speaking at layer con now I have no idea I hope so but yeah let's let's I guess let's save that for later okay next question as far as I know if you were spying you shouldn't be able to return custom values what do you think of that yeah you're right because of the way mockery is is written a spy and a mock or really the exact same thing but I think more generally what you're asking is that if you're spying on something you really shouldn't be able to manipulate it you should really just kind of be listening passively in the background so yeah that that's kind of why I think it's interesting that they've they've denoted the difference between a mock and a spy as kind of it's kind of a testing framework but underneath the covers they're both still mocks and you can you can just as easily still kind of stub out methods and manipulate their values even though you're supposed to just be spying so not a very big differentiation and them in mockery I do think other testing frameworks might make a little bit more stricter of a line there but just quickly coming to mind jasmine which is a which is a javascript testing or an ode testing framework they kind of only have spies but you're also able to kind of manipulate the values or stub them out so again it really boils down to to the testing tool that you're using whether whether or not they choose to to be that strict but generally speaking yeah it shouldn't maybe it shouldn't behave that way okay another question here on a large code base do you normally split up unit tests versus integration tests talking about different types of tests for the same class um do I personally know I don't always split them up you've probably noticed me kind of dodged the the question a couple times here or kind of hinted at maybe the answer I just write tests I try not to get caught up and and you know stubs and mocks and dummies and fakes and spies and and I try not to get caught up in you know unit tests and integration tests and in the end test what I really care about is that is that the code is tested some some of the code might be tested as we've seen here in in kind of an overly strict way in a limiting way and I'm fully fully willing to admit that and then other bits of the code might test in a very loose way so for example if I did write an integration test maybe I'm only testing kind of the happy path right I'm not testing all the little pieces all the different user flows because that would take potentially a lot of integration tests and that that's common that's kind of a common argument against things like integration test is that that you know you got to write a lot of them more they're really slow or whatever I think there's a bunch of ways to mitigate that so again the point being is I try not to get too caught up in them what I try to test is behavior I try to test what matters and you know matters meaning is business critical so we kind of talked about this result free earlier that's something that you know maybe again I'm dealing with very large data sets right so that result free is very important so I want to kind of grin early test result free that's not something that I would necessarily be able to get through an integration test or an end-to-end test unless I was like looking at memory counts like within PHP but there could be all sorts of reasons that the memory you know was was what's getting shifted around in the system so an integration test is not very good for something like that for example you know result free is the thing I absolutely want to make sure that I'm calling or for example that I'm writing a very specific SQL statement right in order to do that I have to kind of do that at this level I wouldn't I wouldn't be able to easily have access to do that from an integration test so I'm only noting the differences to kind of point out strengths and weaknesses but again my goal here is to test behavior I want to make sure that one I call all it gives me back tasks and when I call all and something's kind of messed up you know it it returns an empty array it doesn't throw an exception for like odd things or whatever and so long as I'm testing just that the way in which I test it maybe doesn't matter to me but again if I care about testing free or I care about testing the very exact SQL statement I'm using we're gonna have to get pretty granular with a unit test but those other things as I mentioned could be tested more at a high level with with an integration test so hopefully that helps give you give you some kind of answer there but the point to me is you know tests or tests it's special in the beginning I wouldn't get too caught up and the type of test that that you wrote what matters more is is that you're actually testing your you're already ahead of the game if you're if you're truly testing your code some I don't think many devs do it so ok two more questions and then we're gonna cut it off so what is your definition of an integration test I've heard several different ways people describe them and I'm curious how you do I would say an integration test is I guess by definition is one that tests potentially collaboration between the systems so you're not attempting to test in isolation so you know I guess kind of what we were saying here is is that in this case right I think it's pretty safe to call what I wrote earlier a unit test and so kind of by the the opposing definition here what is a unit test it's something where I'm definitely testing in isolation right so I'm testing a broader stroke of the system or more pieces of the system even though I'm only calling all I would classify that just me personally I'm not saying this is this is a overall definition or but to me it would mean that all is actually collaborating and interacting with with the real parts of the other system I haven't isolated it to just be you know limited to itself meaning you know only using these mock objects and it's not it doesn't have any connection outside of itself so you know quite literally there's a connection here in the code if I were truly connecting to the database and this in this you know example I think that's a pretty classic example of of where people would probably start to call it an integration test it's actually integrating with the database it's actually calling the database getting real records and kind of doing its thing from there and I think because of that it would be very difficult to test the way in which we test it as I mentioned before to test things like free and stuff if you were running an integration test you know if you were testing by truly connecting the database and truly returning a result there's no way for me to kind of stub out and control and verify that free got called with at least the simplicity of kind of this one-liner that we've done here that's not to say it couldn't be tested it's just you'd have to come up with with a different way to do so under an integration test so yeah just to kind of give you a summary answer there integration test really in my mind is anything that truly collaborates with with some kind of other part of the system it's not self-contained right it's it's not isolated which meaning it's it's basically just not a unit test so all right last points and then I'm gonna cut it off so okay let's see oh good you're welcome I'm glad that answered it and then how does this in line assignment work data equals new food data can be passed to other mocks with data example okay so you have this example here you're doing some kind of like in-line assignment yeah I mean obviously that can work just by the nature of PHP right because whatever you assign is kind of just the result of an assignment so the value that's being assigned is is going to be what's returned from an assignment statement so I typically don't write code that mean obviously what you have works something I'm not sure I'm not sure kind of what you're asking here but you know this could work I tend to avoid things like that I think in this case especially of like a chained method call already but that that's really it doesn't communicate very well like that code seems it would seem difficult for me to read as another developer I'd rather see data signed above this service line and then just used like it's used down here where it's like return data with data that's a lot more clear in my mind I don't have to dissect you know each individual line here I think I think a lot of devs would miss that line personally should you always avoid hitting the database in a unit test I think by definition yes I think by definition a unit test probably shouldn't hit the database because then it's truly collaborating with another part of the system which would make it not a unit test so not to be too technical on you but yes I would think for a unit test you would you would try to avoid hitting the database a final question do you think unit tests are necessary if you have integration tests for everything challenge of everything I'm gonna leave you with another JB Rheinsberg room as big in testing he's a bit more into the I guess he's a bit more kind of in Java land but but he has all sorts of courses and so forth but this came obviously from a while back now but and he's received a lot of blowback from it but I would definitely watch maybe the first 15 minutes of this it's pretty it's pretty cool it definitely is very passionate about it so it's it's fun not saying I totally agree with it or anything like that I'm just saying that I think it's I think it's a good for the other side because I think that in some communities integration tests are kind of the way and in some communities you know unit tests are the way and people are really passionate about both in this case he's he's very passionate about integration test being a scam of course again like I mentioned before bottom line is there's there's two sides to every coin I just care that we're testing you know I'm not too worried about how many tests you have of each but I do think again the first 15 minutes of his video will kind of answer your question about you know kind of challenge the fact that you have truly written integration tests for everything so definitely give it a watch I'll be in the slack room but I'm gonna go ahead and shut down the stream
Info
Channel: Jason McCreary
Views: 4,175
Rating: undefined out of 5
Keywords: php, testing, workshops by jmac, phpunit, mockery
Id: kSHF0VvjHK8
Channel Id: undefined
Length: 68min 57sec (4137 seconds)
Published: Wed Nov 29 2017
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.