[0:00] [Music] [0:05] in the last lesson we talked about [0:06] different methods and techniques of [0:08] testing in this lesson we'll actually [0:10] write some unit tests and learn the [0:12] basics of php unit which is a testing [0:14] framework for php first we need to [0:17] install the php unit so let's go to the [0:19] php units documentation and follow the [0:21] installation steps there within the [0:23] installation section we see the option [0:25] to install it with composer so let's [0:27] click on that and we'll copy the command [0:29] and paste it into the terminal so this [0:31] is the command that we're going to copy [0:33] and notice that we're installing php [0:35] unit as a dev dependency which is why [0:37] there is dash dash dev option here so [0:39] let's take this go to the terminal paste [0:42] it in and hit enter after the [0:43] installation is complete we can open the [0:46] composer.json file to confirm that php [0:48] unit dependency was added and we can see [0:51] here that it was added as a dev [0:52] dependency within the required dev [0:55] section we can then run tests using the [0:57] php unit script that is located within [1:00] the vendor bin directory so if we open [1:02] the project files here and open the [1:04] vendor directory and within the bin [1:06] directory we see this php unit script so [1:08] that's what we use to run the tests so [1:10] if we type in vendor bin php unit and [1:14] hit enter we get bunch of available [1:16] options that we can use along with the [1:18] command we can run tests for a specific [1:21] file or an entire folder so we could [1:23] specify a specific test file here for [1:25] example my test.php or we could specify [1:29] an entire directory and you will run all [1:31] the tests within that directory or we [1:33] could use the filter option to run the [1:35] specific test so as you can see here [1:37] there are lots of options at your [1:39] disposal and you don't need to worry [1:40] about all of them you will get used to [1:42] them as you work on phpunit and you will [1:44] probably only use few of them also if [1:47] you did not want to type out vendor bin [1:49] php unit every single time you could [1:51] also create an alias for it to be [1:53] organized we're going to create a [1:55] dedicated directory for tests where [1:57] we're going to place our test files in [1:59] so we're going to create a new directory [2:01] called tests and within the test [2:03] directory we're going to create another [2:05] directory called unit which will hold [2:08] all of our unit tests this way we can [2:10] have our unit tests within its own [2:12] namespace and directory and later we [2:14] could have a directory for integration [2:16] or future tests and so on it is up to [2:18] you how you want to structure your tests [2:20] but this is a pretty common way before [2:22] we create the actual test file let's [2:24] first configure the php unit by creating [2:27] a configuration file php unit [2:29] configuration along with the other [2:30] benefits helps us set some of the [2:32] options that we would otherwise have to [2:34] include within the command so let's [2:36] create the php unit configuration file [2:38] and it's an xml file in xml format so [2:41] we're going to create phpunit.xml file [2:43] within the project root i'm going to [2:45] paste in the basic php unit [2:46] configuration snippet here so let's go [2:49] over it quick as i mentioned before it's [2:50] an xml file so it's an xml format within [2:53] the php unit we specify the options in [2:56] the form of attributes so we're setting [2:57] the colors option to true for color [3:00] coding our test results and we're [3:01] setting the bootstrap script to the [3:03] autoloading script from the vendor [3:05] directory bootstrap script is loaded [3:07] before the tests are run and it can be [3:10] set to a custom bootstrap script if [3:12] needed you can check the documentation [3:14] for other options that you can configure [3:16] for which i'll leave the link in the [3:17] description then within the php unit [3:20] element we have the test suites element [3:22] for better organization and because we [3:24] are only writing unit tests for now we [3:26] only have a single test suite element [3:29] here for the unit tests because we have [3:31] the unit directory here that's what this [3:33] is referencing too later we could add [3:35] another test suite for integration or [3:37] feature tests and so on so if i open the [3:40] terminal now and run the phpunite script [3:42] without specifying the directory it will [3:44] run through the test suites that are [3:46] defined in the configuration file and as [3:48] you can see we're not getting any [3:50] options we're getting a warning stating [3:51] that no tests were executed which makes [3:54] sense because we haven't written any [3:55] tests yet we're going to write some [3:57] tests for the router class that we [3:59] created in the preview section of the [4:01] series if you haven't watched it or [4:03] don't remember it no worries i'll go [4:05] over what the router class does it's a [4:07] pretty simple class where we have a few [4:08] methods available to register different [4:10] routes like a get and post routes for [4:12] example they simply call the register [4:14] method which adds the route along with [4:16] its action to the route's property the [4:18] action can either be an array that [4:20] contains a class name like a controller [4:22] and a method that it needs to run or it [4:24] can simply be a callable then we have [4:26] the routes method that returns the [4:28] registered routes and we have the [4:29] resolve method that resolves the route [4:31] based on the request eri and the request [4:34] method so it's a very basic router [4:36] nothing fancy but just because it's [4:38] simple doesn't mean it should have no [4:40] tests so let's add a new unit test class [4:42] called router test and notice the naming [4:44] convention here we're following the same [4:46] regular class naming convention with the [4:48] test appended at the end let's click ok [4:51] and we're going to extend phpunit's test [4:53] case class which provides the [4:55] functionality for the tests the actual [4:57] tests are written in the form of methods [4:59] within the test class i'm going to open [5:01] the router class in a split view so that [5:04] it's easier to follow and see what we're [5:05] testing the first thing that we might [5:07] want to test is to confirm that a route [5:09] can be registered so we're targeting the [5:11] register method to test if we scroll up [5:13] we're targeting this method basically we [5:15] can call the test method something like [5:18] it registers a route so we'll do public [5:20] function it registers a route and you [5:22] can name tests any way you want it can [5:25] be named something like route can be [5:27] registered or test that route is [5:29] registered and so on i personally prefer [5:31] descriptive names even if they are long [5:34] so it is up to you how you want to name [5:35] your tests when i write tests i usually [5:38] follow the given when then or arrange [5:40] act assert patterns sometimes it also [5:43] helps to write out what it is that you [5:45] are actually testing in words or [5:47] sentences for example let's write out [5:49] what we are actually testing in [5:51] sentences here so given that we have a [5:53] router object when we take action or [5:56] when we call a register method and [5:58] provide the arguments then we assert the [6:01] expected outcome or in this case we [6:03] assert that route was registered so [6:06] let's fill this in now given that we [6:08] have a router object so will the router [6:10] equals new router when we call the [6:12] register method and provide the [6:14] arguments so we'll do a router register [6:17] and in this case it does not really [6:19] matter what arguments we provide because [6:21] we are not testing the resolving part we [6:23] are testing that our route is registered [6:26] so we'll provide some route here we'll [6:27] do the get method and the route will be [6:29] users and the action will be some users [6:32] maybe controller or class and the method [6:35] will be index and for the final part [6:37] where we need to assert we can use one [6:39] of the assertion methods from the php [6:41] units test case class so if we type this [6:45] assert we can see bunch of available [6:47] assertions here we can use assert equals [6:50] to compare the expected value with the [6:52] actual value in this case we'll simply [6:54] do expected array and we'll fill this in [6:57] in a second and the actual value we can [6:59] get from the routes method from the [7:01] router object so we'll do routes because [7:04] as you remember the routes method right [7:06] here simply returns the routes property [7:08] so now we just need to create this [7:10] expected variable so let's create it [7:12] right here and if we look at the [7:14] structure of the routes property if we [7:16] scroll up to the register method we can [7:18] see what the expected value should be so [7:20] let's build up the expected value here [7:23] manually so we'll do get as the key and [7:26] it contains a single route in this case [7:28] and the key of that route is the users [7:31] and the value is users index let's run [7:34] the test and see if it passes and we're [7:37] getting the warning that no tests were [7:38] found within the router test class the [7:41] reason for that is because phpunit does [7:44] not know if this method is a test method [7:46] or just a regular method we can hint it [7:48] using annotation or we could prefix the [7:51] method name with the word test let's add [7:53] the annotation first so i'm going to add [7:56] the annotation test here and run it [7:58] again and sure enough he detected one [8:00] test and one assertion and our test [8:03] passed let's try with the test prefix so [8:05] we can simply prefix this method with [8:07] the test so we can say test that it [8:10] registers a route and we can get rid of [8:12] the annotation and let's run the test [8:14] again and sure enough it still works [8:16] it's up to you which one you want to use [8:18] either way is fine as far as the naming [8:20] conventions go it's also up to you and [8:22] your team what conventions you want to [8:24] follow if you strictly follow psr then [8:27] you would probably use camel case for [8:29] method names so in that case this would [8:31] turn into a camelcase method name and [8:33] there is nothing wrong with that a lot [8:34] of developers along with some frameworks [8:37] follow that convention but for me it's a [8:39] bit harder to read when method names are [8:41] long using camel case which is why i use [8:43] snake case for test methods i'm going to [8:46] revert that back to the annotation and [8:48] let's continue writing some tests we can [8:50] add a test to confirm that get and post [8:52] routes are registered properly when we [8:54] call the get and post methods now you [8:57] could add two unit tests one to test the [8:59] get method and the other one to test the [9:01] post method or you could add a single [9:02] unit test to test both some developers [9:04] prefer to combine them into a single [9:06] test some prefer to have it a separate [9:09] test it is up to you how you want to do [9:10] it but i personally like to have [9:12] separate tests so for example in this [9:14] case we're going to test that a get [9:16] route is registered so we'll do public [9:18] function it registers a get route and [9:22] we'll add another test [9:23] where it registers a post route we'll [9:26] add the annotation to both [9:29] and we'll pretty much copy this entire [9:31] thing here we're going to remove the [9:34] comments and instead of register we'll [9:36] call the get method and remove this [9:38] first argument and everything else [9:40] should be the same so we'll again copy [9:42] this and paste it here we'll replace the [9:44] get with post and we'll replace the get [9:47] method with the post method we can also [9:49] replace the index with store to be more [9:51] appropriate and everything else remains [9:53] the same so as i mentioned some [9:55] developers might prefer to test them [9:57] together so you could do something like [9:59] combine them and register both routes [10:01] and simply build up the expected array [10:04] properly and test it with a single [10:06] assertion i personally prefer to have [10:07] the separate tests because i want to [10:09] test the get method in isolation and i [10:11] want to test the post method in [10:13] isolation as well so let's get rid of [10:15] this here and let's run the test to see [10:17] if it passes so i'm going to run the [10:19] tests here and now we have three tests [10:21] and three assertions and everything is [10:23] passing if we were refactoring the post [10:25] method here and changed it to put for [10:27] example but kept the method name as post [10:30] our test would be able to catch that so [10:32] if we run the test again we see that now [10:34] it failed because this test where we're [10:37] expecting to register a post route the [10:39] expected value doesn't match the actual [10:42] value so then we would go in here and [10:44] fix it up and run the tests again and [10:47] now it's passing let's also add a unit [10:48] test for the routes method to confirm [10:50] that no routes are registered initially [10:53] when we first create the router object [10:55] so we'll do something like public [10:57] function there are no routes when router [11:01] is created so given that we have the [11:03] router object we simply need to assert [11:06] that the routes returns an empty array [11:09] we can use assert empty assertion for [11:11] that so we'll do this assert empty [11:14] router route let's run the test and in [11:16] this case i'm simply going to filter it [11:18] so that it runs the test for this method [11:21] only so we'll do dash dash filter and [11:23] specify the test method and looks like [11:26] our test failed according to the error [11:28] it seems that we're trying to access the [11:30] property before it has been initialized [11:32] if we look at the routes method we see [11:34] that it simply returns the routes [11:36] property right and if we scroll up here [11:38] where the routes property is defined we [11:40] see that it's not being initialized and [11:42] we don't have a constructor where we're [11:44] initializing the routes property we're [11:46] not registering any routes so the error [11:48] message makes perfect sense and a simple [11:50] unit test like this helped us find a bug [11:52] in the code this can easily be fixed by [11:55] initializing the property to an empty [11:57] array either directly in the property [11:59] definition or within the constructor in [12:01] this case we'll initialize it within the [12:03] property definition to an empty array [12:05] and let's run the test again and sure [12:06] enough now it passes sometimes we want [12:09] to prepare some objects or some data [12:11] before tests are run for example in all [12:13] of these tests here we're creating the [12:15] new instance of the router object the [12:17] same way so we're doing a router equals [12:19] new router and we're repeating that over [12:21] and over in all of our tests now this [12:24] usually isn't the problem when we have [12:25] the simple objects like this but it [12:28] becomes tricky when you are passing [12:30] along some dependencies to the [12:31] instantiated objects or you require some [12:34] more setup of the object that is then [12:36] used in most of your tests for example [12:38] if we were passing some dependencies [12:40] here some other objects into the router [12:42] constructor or maybe we were calling [12:44] some more methods here to set up the [12:46] router object and we needed that for all [12:49] of our tests then we would be kind of [12:51] duplicating that block of code over and [12:53] over again we could actually share that [12:55] setup code across our tests by using a [12:58] method called setup so we'll define a [13:00] setup method here and this method is [13:02] called before each test is run we're [13:04] going to create the router object here [13:06] and assign it to the router property so [13:08] we'll do this router equals to new [13:10] router [13:12] and then we can simply replace router [13:14] with this router everywhere else so [13:17] we'll do router [13:18] this router and replace all and we'll [13:21] also add the router property and then we [13:24] can scroll down and get rid of the extra [13:26] lines so we'll drop this because we no [13:28] longer need it we'll also get rid of [13:30] these comments since we no longer need [13:32] them we'll get rid of this scroll down [13:34] get rid of that and technically we could [13:36] also get rid of this but i'm going to [13:38] leave that in because this unit test [13:40] specifically tests the scenario where [13:42] we're creating a new router and we're [13:45] confirming that it has no register [13:47] routes so yes this would still work if [13:49] we get rid of this this would still work [13:51] but in this case i would prefer to [13:53] instantiate a new router object just in [13:55] case we were doing something with the [13:57] router object in the setup method so [13:59] we'll do router and router or if you [14:02] want to make it in a single line we'll [14:04] drop that and do it this way let's clear [14:06] this out and run the tests to make sure [14:08] that everything is still working and [14:10] sure enough everything passes we have [14:12] four tests and four assertions we could [14:14] also define a method called tear down [14:16] which is called at the end of each test [14:19] even if the test fails tear down can be [14:21] used to clean up heavy objects or some [14:23] external resources that were created in [14:26] the setup method in this case we don't [14:28] need teardown method because we're [14:30] working with a simple object so we're [14:31] not going to create it alright let's [14:33] move on to the result method which has a [14:35] bit more logic to it the first test that [14:37] makes sense is to test that a route not [14:39] found exception is prone if we scroll [14:41] down to the result method we see that [14:43] route not found exception can be thrown [14:45] for multiple reasons one is when the [14:47] action is not set which means that it [14:49] could either be the wrong request method [14:51] or the wrong route and other reasons [14:53] could be that a class doesn't exist or [14:55] method doesn't exist and so on also just [14:58] a quick note here this check here is [15:00] array check is technically not needed [15:02] because we are essentially checking if [15:05] the action is either callable or array [15:07] and it cannot be anything other than [15:09] callable or array because we have type [15:12] hinted right here right here and right [15:14] here so technically action will not be [15:17] anything other than callable or array so [15:19] we could drop this because if it was [15:21] callable it would execute this block and [15:23] because it's not callable then it's [15:25] assumed that it's array so we could [15:26] technically drop that but before we drop [15:28] this i'm going to write the test so that [15:31] we can refactor it and be sure that our [15:33] tests still pass now we could write a [15:36] separate unit test for each of these [15:38] scenarios where the route not found [15:40] exception is thrown or we could simply [15:42] use something called data provider and [15:45] provide different cases to the unit test [15:47] and phpunit will run the test for all [15:50] the given cases so let's create a unit [15:52] test here called something like eat [15:54] throws route not found exception and [15:57] let's register a couple of routes here [15:59] so we're going to register a post route [16:01] for the user's endpoint which will [16:03] execute the store method on some kind of [16:06] users class and then we'll also register [16:08] the get route for the users and this [16:10] will simply be index now this is the [16:13] given right so then what we need to do [16:16] is that we need to call the resolve [16:17] method so when we try to resolve a route [16:20] we want to assert that route not found [16:22] exception is thrown so we're going to do [16:24] this router resolve and we'll pass in [16:27] some kind of request uri and some kind [16:30] of request method now i'm not hard [16:32] coding this because if i were to hard [16:34] code this then you would only be testing [16:36] a single scenario right a single test [16:38] case but we want to use the data [16:40] provider where these two arguments here [16:42] are given by the data provider before we [16:45] do that though we need to assert that an [16:47] exception was thrown to assert that an [16:49] exception is wrong we can use expect [16:51] exception method so we could type this [16:54] expect exception and as you can see we [16:57] can also assert exception message code [16:59] and so on but we only care for the [17:01] exception class for now so we'll do [17:03] expect exception route not found [17:05] exception and the expect exception [17:07] assertion has to happen before calling [17:09] the method that actually throws the [17:11] exception otherwise if we did it the [17:13] other way and we put expect exception [17:15] after this would throw the exception and [17:17] it would stop the execution so we need [17:19] to add this beforehand so now we have [17:21] our test set up properly we are [17:22] asserting that exception is thrown the [17:24] only other thing left is to provide the [17:26] data provider here where we provide the [17:28] request eri and request methods a test [17:31] method can accept x number of arguments [17:33] and it can be provided by data provider [17:36] method so we could say that this test [17:37] method expects request dri and request [17:42] method now this of course will not work [17:44] because we haven't specified the data [17:46] provider yet we can add the data [17:47] provider by using the annotation as well [17:50] so we'll do data provider followed by [17:52] the method name whose return will be [17:54] passed as arguments to the test method [17:56] so we'll call this route not found cases [18:00] and as you can see the ide is [18:01] highlighting it here because this method [18:03] doesn't exist so we need to create this [18:05] data provider method based on the [18:07] definition from the documentation this [18:09] method needs to be public and must [18:10] return an array of arrays or it must [18:13] return an object that implements the [18:15] iterator interface which yields an array [18:17] for each iteration so let's create this [18:19] method here we'll do public function [18:21] route not found cases and we'll return [18:24] an array now this needs to return an [18:26] array of arrays so basically it's an [18:28] array that contains arrays as values and [18:31] these array values are the basically [18:33] each individual cases and whatever you [18:35] provide here are going to be passed as [18:38] arguments to the test method that uses [18:40] that data provider so if we say here foo [18:44] bar who is going to be passed in as [18:46] request eri and bar is going to be [18:48] passed in as request method so let's [18:50] write out some failing cases that would [18:52] result in route not found exception so [18:54] the first two cases are when request [18:56] method is not found and the route is not [18:59] found we have the post and get routes [19:00] for the index so the first one is easy [19:03] we'll do users [19:04] and put now in this case the route is [19:07] found but the request method is not [19:09] found the other case would be invoices [19:12] and post where the request method is [19:14] found but the route is not found the [19:17] next case would be when the class does [19:18] not exist now providing a failing case [19:21] for this scenario is actually pretty [19:22] simple because the user's class doesn't [19:24] exist right this is just a string it's [19:26] not really a class so we could provide [19:29] the proper route and a proper method but [19:31] it will still fail because the class [19:33] does not exist so we'll do users and get [19:36] and this is going to get us up to here [19:39] but it's going to fail because class [19:41] does not exist and it will throw the [19:42] route not found exception and for the [19:44] last case where the method doesn't exist [19:46] for that we need to somehow simulate a [19:50] way where the class exists and i don't [19:52] want to hard code some controller here [19:55] or something like that it wouldn't make [19:56] sense because we're doing unit testing [19:58] and we don't want to depend on other [20:00] classes like controllers for example so [20:02] what we can do is that we can use [20:04] anonymous classes to kind of simulate a [20:06] controller so we'll define some kind of [20:09] class here like users and set it to [20:11] anonymous class then within the [20:13] anonymous class we'll define some kind [20:15] of method let's say we'll have a method [20:17] to delete an user so we'll do a delete [20:19] and this returns true and now instead of [20:22] using users here we could simply replace [20:24] these users with users scope resolution [20:27] operator class so now when we make a [20:29] post request to the user's endpoint it [20:31] should execute the store method on the [20:34] user's class and the user's class exists [20:36] because we're simulating it as an [20:38] anonymous class but the store method [20:40] does not exist so essentially we should [20:42] get here and this should fail and you [20:44] should still throw this exception so [20:46] we'll add that failing case here so [20:48] we'll do users and post let's run the [20:51] test to make sure that it works i'm [20:53] going to filter it again to only run [20:55] this test so i'm going to copy that and [20:58] filter and sure enough as you can see [21:00] four tests were found and four [21:02] assertions now these four tests are not [21:04] the same four tests as the previous test [21:06] result that we have this was four tests [21:08] entirely for the router test class but [21:11] these four tests are just the four test [21:14] cases here so each individual case here [21:16] is its own test if you run the test [21:18] without filtering you'll see that it [21:20] should say eight instead of four and [21:22] sure enough we have eight tests and [21:24] eight assertions now we can be confident [21:26] that if we refactor this code and [21:28] something breaks our tests should be [21:31] able to catch such issues so we run the [21:33] tests everything is still fine and if we [21:35] keep refactoring and we make some kind [21:37] of mistake where we only throw the [21:39] exception if the class doesn't exist [21:41] then this will fail because it is not [21:44] throwing an exception when the method [21:46] doesn't exist so as you can see we are [21:48] getting a failing test saying that data [21:50] set number three and the data set start [21:52] from zero so zero one two three it's [21:55] this one and these are the values of the [21:57] arguments and the message says that it [21:59] failed asserting that exception was [22:01] thrown so we'll revert that back run it [22:04] again and it passes so as you can see [22:06] the data providers can be used to [22:08] simplify tests and test against multiple [22:10] cases you can even reference to external [22:13] data providers say for example you had [22:15] this data provider method extracted in a [22:17] data provider class you could use fully [22:20] qualified class name to reference this [22:22] method that way multiple test classes [22:24] could use the same data provider for [22:26] example we could have a directory like [22:28] data providers here so we'll do data [22:30] providers and we could add a new class [22:32] here called something like router data [22:35] provider and simply extract this method [22:38] to here and then we can reference it by [22:40] using fully qualified class names so we [22:43] need to add the fully qualified class [22:44] name here so we need to do tests data [22:47] providers router data provider scope [22:50] resolution operator route not found [22:53] cases and now as you can see within the [22:55] id it's even clickable and i can click [22:57] into it and this should still work so if [22:59] i run the tests again we see that our [23:01] test fails so let's figure out why our [23:03] test is failing the error says that the [23:06] router data provider class does not [23:08] exist but we have created the class and [23:10] we have the proper namespaces but it [23:12] still says that it doesn't exist so [23:14] what's the issue let's open our [23:16] composer.json file and within our [23:18] composer.json file we see that we are [23:20] using the auto loading for the app name [23:23] space and the app directory we need to [23:25] do the same thing for the test directory [23:28] because we have the test directory out [23:30] of the app directory so it's not using [23:32] the same auto loading as this so we need [23:35] to add another autoloading section here [23:37] so we'll duplicate this and we'll do [23:39] auto load dev because we only need it [23:40] for the development and we'll change [23:42] this to tests and tests now we'll run [23:45] the composer dump auto load [23:48] let's run the tests again and everything [23:50] is back to normal data providers are [23:52] cool but don't try to use data providers [23:54] for everything basically don't overuse [23:57] it try to look at your code and see if [23:59] the data provider makes sense it's [24:01] useful for things like validations where [24:03] you want to provide valid data and [24:05] assert that everything passes or you [24:07] want to provide invalid data and assert [24:10] that it fails kind of like what we did [24:12] here where we're providing the invalid [24:14] cases and asserting that an exception is [24:16] thrown for all those cases now of course [24:19] this router class itself is not perfect [24:22] it is not meant to be used in production [24:24] this is just a simple router example [24:26] that we wrote in the previous section [24:27] and i decided to write tests for it [24:30] because it was better than writing tests [24:32] for some kind of full bar examples also [24:34] in a real application you would have [24:36] more complicated router because you may [24:38] want to resolve other possible methods [24:41] like put and delete and maybe accept the [24:43] arguments within the route that he would [24:45] then pass to the controllers and so on [24:47] also in this case where we're throwing [24:49] the route not found exception instead of [24:51] throwing route not found exception here [24:53] we could be throwing different type of [24:55] exceptions and then we could have [24:57] different tests for it to test [24:59] individual cases for example in here if [25:02] the class doesn't exist we could throw [25:04] class not found exception if the method [25:07] doesn't exist we could throw method not [25:09] found exception and both of them could [25:11] extend from the route not found [25:12] exception and so on let's write couple [25:14] more tests for the result method so far [25:17] we've written tests where we're testing [25:19] the failures right we're testing this [25:21] and we're testing essentially this but [25:23] we haven't tested this section and we [25:26] haven't tested this section when it [25:28] actually passes so the first test we're [25:30] going to write is to assert that it [25:32] resolves properly when the action is [25:34] callable so we'll do something like this [25:36] we'll do public function it resolves [25:39] route from a closure we'll register a [25:42] route here so we'll do this router get [25:44] and we'll use slash users and we'll pass [25:47] in the closure as the action instead of [25:49] an array and we'll simply let it return [25:51] one two three to indicate that it's [25:53] returning some type of user ids or [25:55] something it doesn't matter what we're [25:57] actually returning in this case just [25:58] that we're returning something that we [26:00] could then assert now this is the given [26:03] so when we actually call the resolve [26:05] method and pass in the correct request [26:07] uri and request method we want to assert [26:10] that this is what gets resolved to so [26:13] this is what needs to be returned from [26:15] the resolve method so we'll do this [26:17] assert equals array of one two and three [26:21] and the actual result will be this [26:23] router resolve request eri is users and [26:27] the request method is get let's clean [26:29] this up a little bit so it's easier to [26:31] read and let's test this out we're going [26:33] to open the terminal and let's run the [26:35] tests and sure enough now we have nine [26:37] tests and nine assertions which means [26:39] that this test also passed if we [26:41] commented this out for example and run [26:43] the tests we see that this test will now [26:46] fail so let's revert that and let's [26:48] write the last test for today which [26:50] should test that a route is actually [26:52] resolved properly so we could again use [26:54] the anonymous class for that so let's [26:56] write the test first we'll do public [26:58] function it resolves [27:01] route and we're going to use the [27:02] anonymous class here as well so we're [27:04] going to copy the anonymous class from [27:06] the previous test and paste it here and [27:08] we'll just change the method to [27:09] something like index and we can do the [27:11] return of array and we can actually [27:13] return the same thing one two and three [27:15] though as i mentioned before the return [27:17] doesn't really matter for this example [27:19] because we're not testing controllers [27:21] we're testing the router class and [27:23] ensuring that a route gets resolved [27:26] properly then we're going to register a [27:28] get route for the users and we're going [27:30] to use the users as our class and the [27:33] index as our method and finally we'll [27:36] assert that an array of 1 2 and 3 equals [27:39] to the actual resolved value and that [27:41] would be this router resolve request eri [27:44] is users and method is get let's run the [27:47] tests and make sure that it's passing [27:49] and sure enough we have 10 tests and 10 [27:52] assertions which means that all of our [27:53] tests in the router test are passing [27:55] before we wrap up for today i want to [27:57] talk about the assert equals method [28:00] because assert equals does not do strict [28:02] comparison and it can sometimes result [28:05] in false positives which can give you an [28:06] impression that you have no bugs because [28:09] your tests are passing but you actually [28:11] have some bugs for example if we changed [28:13] one of the elements from the returned [28:14] array here to something like string one [28:17] when we use assert equals this will [28:19] still pass so if we open the terminal [28:22] and run the tests everything is still [28:24] passing but technically should not pass [28:26] because this and this are not the same [28:28] this is a string one and this is an [28:30] integer one in this specific example it [28:32] does not really matter because it's not [28:34] the bug in our code we're creating this [28:37] anonymous class within the test and [28:38] we're asserting it here so it's not a [28:40] big deal but if this was a method on a [28:42] class within the app that was expected [28:45] to return zero for example but instead [28:47] it returned boolean false using assert [28:50] equals would make test pass so it could [28:52] result in false positives so for example [28:54] if we change this to boolean and we [28:57] returned false and simply think about it [28:59] that this is an actual class within our [29:01] app class and not an anonymous class so [29:03] think about it that it's a controller [29:05] maybe or something like that and we're [29:07] asserting that the return value of the [29:08] resolve is zero now this is going to [29:11] pass if we run vendor being phpunit we [29:14] see that everything is passing so you're [29:16] getting the impression that everything [29:17] is fine with your code but something [29:19] somewhere might blow up because it's a [29:21] false positive and it's better to use [29:23] strict comparisons the way you can do [29:25] that is that there is another assertion [29:27] method called assert same and assert [29:30] same in addition to comparing the value [29:32] it also compares the type so if i run [29:34] this again we see that this test fails [29:36] and it says that it failed asserting [29:38] that false is identical to zero and the [29:41] key word here is identical so [29:42] essentially a third same is pretty much [29:45] the same as identity operator so you [29:47] could think of it as assert equals as [29:50] double equal sign comparison or the [29:52] comparison operator and assert same is [29:55] same as the triple equal comparison or [29:57] the identity operator i personally use [30:00] assert same in most cases and would [30:02] recommend it as well just be aware that [30:04] when comparing two objects a certain [30:06] equals will pass when comparing two [30:09] different objects with the same property [30:11] values while assert same will fail since [30:14] assert same would only pass if two [30:16] variables were pointing to the same [30:18] object and if you're not sure what i'm [30:20] talking about you can watch one of the [30:21] previous lessons where i talked about [30:23] comparing objects using comparison [30:25] operator versus identity operator and [30:28] the link for that video is in the [30:29] description so what i'm going to do now [30:31] is i'm going to revert this back and i'm [30:33] going to replace assert equals with a [30:35] third same everywhere so we'll do a [30:37] third equals replace with a search same [30:40] and replace all and let's make sure that [30:42] everything still passes everywhere we're [30:44] going to run the test again and [30:46] everything is still passing so this is [30:48] it for this video thank you so much for [30:50] watching in the next video we're going [30:52] to continue with the php unit and cover [30:54] things like test doubles stubs and [30:56] mocking if you enjoy my tutorials please [30:58] give my videos thumbs up share and [31:00] subscribe and i'll see you in the next [31:01] one