PHP Unit Testing with PHPUnit | Automated PHP Testing Tutorial [2021]

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
it's gary clark tech in this recording i'm going to cover all the essentials of testing php code using php unit so we'll cover setup and configuration writing and running tests different kinds of assertions test setup and turn down methods and testables including mocking objects and methods plus quite a lot of good ideas on how to test your code along the way before we get into that let me just say that i record in high resolution so no need to watch on a blurry screen choose high definition if that will work for you would you like to join a chrome group of php developers and take your skills to a new level if that sounds like you all you need to do is subscribe and click the little notification icon and welcome for this next section we're going to need a library called phpunit which is the standard library for testing in php so the vendor is php unit and the library is also called php unit so composer require and then i'm going to do hyphen hyphen dev because i only want to require it for development purposes and then it's php unit forward slash php unit and then we let that install okay so what i'm going to do here is i'm going to create a folder called tests and this is where i'm going to keep all the tests i'm going to create automated tests for testing my code and notice how i name my test i it ends with the word test so i'm calling this one example assertions test and this will extend a class called test case which comes from php unit so as you can see here test case comes from php unit framework test case the actual individual tests themselves can be found in the form of methods or can be created in the form of methods typically what we will do is we'll create a method which begins with the word test and that will help php unit know that this is a test and it also needs to be public so public function test this all that another way you can tell php unit that it's a test is to actually annotate it like this which we'll do later on but for now we'll stick with what is the most common way of denoting a test and we'll do it by prefixing it with the name test so what i'm going to do is i'm going to run this and the way you do this is php vendor forward slash pin forward slash php unit and then the path to the file test example assertion test and we get this so php unit has run but it's saying the test did not perform any assertions which is understandable because the method is actually empty let me explain what this vendor bin php unit thing is so inside of our vendors folder we have this bin folder binary folder and then we have the php unit file which executes our tests let me show you how you test something so typically what you do is you perform an action you run a piece of code and you make assertions about the code which is just run i'm going to show you a very quick and simple way of how to make an assertion so i'm going to create two strings which are identical to start off with and then we're going to use a method which we're inheriting from the test case class and this is called assert same so the first argument you pass in is what you're expecting and the second argument is your actual result and it will compare them against each other and tell you if they are the same or not so these are both the same so i should get a test pass that's from the test php vendor bin php unit and then our path to the test or the test file run that and so if we look at the bottom here it's saying one test one assertion and that means it's passed because it would tell us if there are any failures if it had failed so one test one assertion let's now do a test which fails so you can see what that looks like and i'm going to make a similar looking string so this is typical or something which might be caught by testing you use an upper case instead of a lowercase that kind of thing happens all the time so pretty good example and we're going to do another session because you can put as many assertions as you like in the test i'm going to assert that string2 is the same as string3 the test okay so this time it's saying one test has run we performed two assertions but we had one failure and what's really handy about this is it actually tells you where you've gone wrong so saying you're expecting this so argument one testing lowercase but what you actually got was this testing uppercase so testing can really be a very good safety net for catching little things like that let's add a second test this time we'll do something numeric and we'll test that two numbers add up so for this one we'll use a different assertion we're going to use one called assert equals and we'll just do it all in one line we don't need to have any setup so this assert equals and we'll go with 10 for expected and then 5 plus 5 for the actual i'm just going to remove this second assertion in the first test so we should have a total of two tests and two assertions like so everything's passing because we don't get any failures two tests two assertions and you'll see these two little dots here that means that two tests have run one test two tests one assertion two assertions let's make things a little easier to read we can add a colors flag on the end so hyphen hyphen colors run the tests and then if it passes we get green and if it fails we get red so let me illustrate that also we'll say that five plus one equals ten which obviously doesn't and there we get a big angry red failure and as you can see we have a red f for the second dot meaning that the second test failed so pretty good feedback let's move on to testing objects and classes this is an object-oriented php course so let's now test an object i'm going to create a new class called cart and i'm going to give this a couple of properties one of them will be just a normal public property called price so public float price and then i'm going to have a static property which will be tax so public static float tax and i'm going to set a default value of 1.2 and so the way i'm going to calculate the net price for a cart will be to multiply the price by the tax so i'm creating a method called get net price and i'm returning at this price multiplied by self tax let's now create a test where we can test this so inside of the test folder and creating a test file called cart test again this will extend test case so php unit framework test case and i'm going to add a method where i can test that i'm receiving the correct net price for the cart so test correct net price is returned i'll require the cart file shortly we'll come on to autoloading this stuff but for now let's just focus on one thing at once so cart equals new cart and then i'm going to set the price i'll set it to 10 i'll keep the numbers simple so it's easy for me to know if i'm right or wrong i'll obtain the net price by saying cart get net price and so what should happen here is behind the scenes it multiplies the price by the tax so my price is 10 and i'm multiplying that by a tax of 1.2 that should give me 12. expected is 12 and then i pass a net price as the actual over to the terminal run the test one test one assertion and that has passed let's do our colors and that looks much better easier to see one test one assertion all good let's add another one now and this time instead of prefixing it with the word test i'm going to write this out in snake case and i'm going to use the test annotation this is how i do it quite a lot of time because i believe it looks more legible it's easier for me to read especially if it's going to be a long test name and i do use long test names because i like them to describe what i'm doing like a a little story just makes it clear what exactly is being tested okay so i've moved the require cart to above the test because i'm going to need the cart in this test also and what i'm testing here is that the tax and value can be changed so it's a static property i'm going to set it to 1.5 just going to give myself a little space here and then what i'm going to do is test that i get a new value based on 1.5 rather than the default of 1.2 which we tested in the first case so cart price equals 10 this should give me a net price of 15. so i'm going to set the net price to what comes back from the cart get net price method and then we just need to do an assertion like we did with our first test so this assert equals and i'm going to say 15 and then for the actual i pass in the net price variable just before i run the test i'm actually going to rename it to make more sense so that it says the cart tax and value can be changed statically i think that makes more sense to me i'm going to show you another little flag you can use here if i do filter followed by the test name it will just run that test as you can see there one test one assertion now let me show you how we can configure php units so we don't need to be passing things like colors every time we can actually create a config file which does this for us and it's called phpunit.xml so it's in xml format the first line is standard you'll put that on all your php unit xml files and then we create a php unit tag then a test suites tag and then a test suite which i've called tests so you can have as many test suites as you like we've just got one and we're saying that the tests folder is the directory for our tests and then we can give attributes so on the php unit tag we're going to say colors is true and then we'll go back to our test and we can actually remove this flag now because this should be done for us run it again and there we go let's look at some more attributes this one is bootstrap so i can point it towards a custom bootstrap file which i create myself but what i'm going to do is i'm going to point it to the vendor auto load file and then having done that i can also load my classes in my test so i won't need to require things like the cart like i'm currently doing in my cart test i'm creating a source file or folder and that's where i'm going to place my classes like we've done up to now i'm going to give this a name space of app and what i want to do is map the app namespace to the source folder so over to composer json underneath our requirements i'm going to add an autoload key and then the psr4 standard key and then what i'm doing is i'm creating a mapping between app and the source folder now when you change your config you need to dump the auto load first before i do that we'll just change some of this stuff to make sure that is using the cart from the app namespace so everything's looking good there but the tests won't pass yet we need to do composer dump hyphen auto load and the dash o means optimized okay our test should pass now if we run it so run that again and everything's running exactly the same as it was before except we're not requiring the cart file now as you can see when we run our test we get this file here phpunit.result.cache that is for when you want to cache the results of tests say you have a big project it'll make your tests run quicker but we have a small project so i'm going to add this attribute here cache result equals false and that means that we won't be creating and writing to that file anymore let's run the tests okay all green two tests two assertions now let's look at the scope of running a different test so we run just the test folder and we get four tests then we can specify the cart test where we run two tests and then we can go more fine-grained by just specifying an individual test by using the filter flag and we'll do that now so filter and then just the name of the test one test one assertion so just to be clear you can run it on the folder you can run it on individual tests or individual test files and you can run it on individual tests this recording is actually a small sample from my complete object-oriented php developer course if you're interested in studying the full course the link is in the description down below now quite often you realize that you're repeating your setup in your test for example here and creating a cart in both of my tests not a problem if it's only something small like this but the car i might need to add properties to it might be a lot more elaborate in those cases we can make use of one of the built-in methods called setup and so if you're going to need the same setup when you're on each test you can actually place it in this and this setup method will run before each individual test so that's what we'll do we'll create a new card before each test and then it means we don't have to duplicate the same thing in all of our tests we can remove those now and we'll replace it with this cart instead of cart we'll do that for all the others all we need to do then is just run the tests and make sure nothing breaks and we have the same result two tests two assertions all green but here's a question what do you suppose would happen if we rearrange the order of our tests as you can see in the cart tax value can be changed statically test we are setting the tax to 1.5 the tax is normally set as default to 1.2 it's a static property so it means it's being set on the class and not the object so what do you suppose will happen when we run this test what actually happens is we're failing to assert that 15 matches the expected 12 in the second test because we still have some hangover from the first test where we set it to 1.5 or we set the tax to 1.5 so just like we have a setup method we also have a method which takes care of things like this where we can reset our system to the way we want it after the test is run and it's called teardown and so what i'm going to do here is just reset the tax to 1.2 and hopefully when we run our tests again back to green two tests two assertions all good let's now move on to testing for when things go wrong or testing to make sure that your system catches things which go wrong in order to do this i'm going to add a new method to the cart class and that's going to be an add to price method and all it's going to do is take an amount and add it to the running price so fairly straightforward there but you'll notice i have type hinted int so it needs to be an integer okay so there's different ways you can test exceptions i'm going to show you the most common way php unit has some methods built in where you can expect an exception to be thrown and so before you perform the act you actually put in your expectation so i'm first saying that i expect a type error to be thrown when i run the code which then comes after this and i'm going to say this cart add to price and i'm going to use a string instead of an integer and so this means that the test should pass because the expected exception will be thrown hopefully run that one test one assertion all good okay now i'm going to show you a preferred way or my preferred way of testing exceptions before i do that i'm going to grab my old friend the symphony var dumper just so i can dump some stuff out on the fly that's for dumper installing this technique that i'm going to show you now i'm not going to take the credit for this i got this i learned this from adam mathen that's the same adam watson that created tailwind brilliant developer i recommend all these resources for learning and basically what you do is instead of saying i expect this exception to be thrown i just wrap my code in the try catch block like i would do anywhere else in my code except i'm doing it in the test and if an exception is thrown then i can catch um that exception or the error and make assertions of it inside the catch block so what we'll do is we'll just run this and dump that out for now to make sure that that is happening and it is so this is our error being uh dumped out here from the catch block and we'll just copy a bit of this message so we can make assertions against that and so underneath where i run my code and you can use a method from php unit called fail which purposefully fails your code which is handy for a scenario like this if i get past the add to price method it means something's gone wrong an error hasn't been thrown where it should have then what i'm going to do in the catch block is just make assertions and what i'll do is i'll use one called assert string starts with and i'll paste in that little bit of the error message there and then you just need to call get message on the error so just to be clear we're going to try to use a string instead of an integer that will throw this error which will be caught and then we make an assertion about that error that's from the test one test one assertion all green now i'd like to talk about something called test doubles so it's going to be a bit of setup so just code along with me while i explain what testables are testables are for when you have or you want to test part of your system which might be dependent on another part of your system and you don't actually want to be carrying out the functionality which that system that part of the system does for example like we have here querying a database reading or writing from a database other examples might be sending emails you don't really want to be firing off real emails as part of a test so what you do is you replace that functionality with fake functionality or mock functionality and that's what we're going to do for our examples we're going to use mox i've created a test class called a mock product test that extends test case and what we're going to do here is rather than query the database for an array of products we're just going to mock that and make up our own array of products and say that when this method is called on this class just send me back this mocked array of products rather than real ones from the database and i'll work with those in other in order to test other parts of my system the way we create mocks is like this this creates mock which is a php unit method and then we pass in the name of the class which we want to mock and so now we have a mock repo and the way that this works is that the public methods on that repo now instead of being executed what they do is they just return null by default however we have type hinted ours and we type hinted an array so what we'll get back is instead of an array of products we'll just get back an empty array let's go and test out that theory and make sure i'm not talking nonsense and there we go so instead of getting an array of products we just get an empty array just to be clear on that let's go and have a look at the fetch products method so you can see we're typing a return of array let's say that we want a string back and if we're going on the test again in this case we get an empty string if we don't type into any kind of return what you will get is null just to prove that the internals of the method are not being hit we'll try and dump something out on the test and as you can see we're not hitting the inside of that method it's just giving us a string back each time that's not very useful on its own what we actually want to do is say that when we hit the fetch products method on our mock what we want to get back is a certain result so i've created a mock products array then with mock repo we have a method called method and we pass into that the name of the method which we expect to hit and we can say this is what i want you to return and what i will say to this is that i want it to return my mock products array so i'll just rearrange the order of things here and so what i'm saying here is when the fetch products method is called on the mock repo please return me this mock products array dump that out that seems to be working that looks good and so i'll just make a quick assertion on one of the items in that array just so that we have a passing test what we'll do is we'll just assert that the name property of our first product in the array comes back correctly now i know what you're thinking you're probably thinking that this is the most pointless test ever but like i said when i introduced testables the whole point is to replicate the functionality of a dependency a dependency of the part of the system which you are actually testing and that's what we'll do now so i'm creating a class called inventory and this will get some products the way it's going to do that is you've guessed it it's going to use the product repository get the products from the database and store them on a products property in the inventory class first off we're going to new up a products repository now if you think back to the lessons where we've covered dependency injection you might already be thinking this isn't the best way to do this but we'll go along with it we'll do it this way first and then we're going to do it a better way shortly so we'll set this products to products repo fetch products and in fact i haven't named this method very well at all with setting products not getting products so i'm going to change the name of that and then i'll also have a get products method which we might need to make test assertions shortly so we'll make sure that we have that also and that will just return the products from this class now let's go over and create a test class where we can test the products can be set so into tests and we'll just call it inventory test and like i say the first test that we'll have is one which just checks that we can set the product property to an array of products i need to extend test case of course so make sure we'll do that when i'm writing tests i like to just sketch out what i'm going to be doing i'll just call these steps set up do something and make assertions if you're doing like behavior driven development you might see given when then or you might sometimes see a range act as it doesn't matter what you call them as long as it's understandable to you and the developers using the code so our setup is to create a new up an inventory class and then on the inventory we're going to set products so hopefully now you can see where we have that dependency and why it might be a problem to us in order to set products we need to rely on the products repository to make a query to the database now sometimes you might have a test database but you would still need to populate it with products before you run the test in our case this is a small app we're making the decision we don't think it's worth doing that and so we're going to use a mox instead so i shall copy my mock products repo and the mock products array from the mock products test i'm just going to paste in to my inventory test but we have a problem here because my products repo is hard coded into the set products method which means that i will be unable to actually swap it out for a mock repository but this is good because the test has encouraged me to think about writing better code or to approach my code differently in a way which is more flexible so what i'm going to do is i'm going to have a products repo property on my inventory class and i'm going to inject a product repository into my constructor you could even take this a step further and have a repository interface but we'll not do that we'll keep it lean and mean for the purpose of the test so that we don't get sidetracked but what i will be able to do now is to inject the mock repo into the constructor instead of a actual product repository i can remove the instantiation of my products repo in my set products method and then we'll go back over to the test and i just need to switch these around and then i'm going to pass my mock repo into the constructor and that's our setup so this should work now let's go and make some assertions so first off i'm going to check the name of the first product which comes back and that should equate to acme radio knobs and the second one should be apple iphone that's what's good to go let's go and run the tests perfect one test two assertions just for a quick sanity check i'm going to change this expectation and that's great so i changed it to i phones with a zed and it said that didn't equal iphone so that's all working very well we'll just get everything running back to green before we move on okay now we know what this does and what it's expected to return the fetch products method is returned as a mock products array but how do we actually prove that it's been called how do we know that we're not getting these products back from somewhere else the way to prove that the method is being called is to use expect and so you can pass in how many times you expect that method to have been called so we're only expected to call once we run the test and that works other methods you can use are exactly where you can specify exactly how many times you were expecting that method to be called throughout the process so if i change that to two this should fail because we're only expecting it to be called once and as you can see failed for method fetch products when invoked two times method was expected two times but only called once other little handy methods are things like this never so we're saying we expect it never to be called but of course it does get cold i'm going to change it back to exactly one because that's my preferred way of doing it i prefer that to actually saying once though it's meaningless it doesn't matter whether you use once or exactly one let me now show you something else so if i run all the tests in the folder by just saying it tests as you can see seven tests have been executed you can actually group them and run your tests according to the group that you put them in so i'm going to put this test in a group called db because it's connecting to a database or is pretending to connect to a database i'll find my other test in inventory test which is also connecting to a database and i'll put that in the db group and then to run just the tests in that group i can do hyphen hyphen group followed by the name of the group and as you can see i'm now only running two tests i can also run everything except the tests in that group by using exclude hyphen group group name again as you can see five tests i've run there instead of the seven total tests so that's a lot of the php unit theory out of the way we're now going to put this to practice using something called test driven development let's move on i hope you've enjoyed watching this one as much as i've enjoyed making it for you if you want to put some of what you've just learned to more practice and also learn some test driven development along the way you can do so in my complete object oriented php developer course the link is in the description down below and one last thing if you want youtube to show you more of my content all you need to do is subscribe and click the little notification icon i release new material every few days details of my schedule can be found on the community tab of my youtube channel homepage you
Info
Channel: Gary Clarke
Views: 9,486
Rating: 4.9849057 out of 5
Keywords: Gary Clarke, object oriented php, Object Oriented PHP Tutorial For Beginners, php programming tutorial for beginners, php object oriented programming tutorial for beginners, object oriented php full course, php tutorial, PHP OOP Beginner tutorial, learn php for beginners, phpunit, PHP Unit Testing with PHPUnit, Automated PHP Testing Tutorial, php tutorial 2021, phpunit mock, phpunit testing tutorial, phpunit setup teardown, php testing tutorial, phpunit configuration
Id: kkU43JdJQBE
Channel Id: undefined
Length: 30min 41sec (1841 seconds)
Published: Mon May 10 2021
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.