This is an example of using Phiz3 in PHP to make a simple restful JSON API. The API's goal is to return a representation of the calendar month as a JSON object showing weekday names. The JSON object will be an array structure with the month's day used as a key and each day's value containing the weekday name.
- (preparation): Collect some requirements (i.e. the paragraph above has a description of the problem)
- (preparation): Jot down some ideas about the parts needed to solve the problem describe
- (coding): Now let's translate this into some unit tests
- (coding): Write code to pass the unit tests
- (accessment): Does this meet the requirements of step 1, if not repeat
Preparation
Restate the problem you're trying to solve
Let's restate the requirements described in our owning paragraph. We want a Web base API that will transform a date request into a representation of the month with corresponding weekday names.
Identify the parts you need to solve the problem and what might be helpful
- We want a RESTful API - PHP's $_SERVER['PATH_INFO'] will give us the restful arguments to the API request. We'll do this in our event loop.
- We want to be able to input a date - We could use a YYYY/MM/DD structure to represent the date in a RESTful fashion e.g. api/2009/09/01
- We want to JSON output - In September 2009 the JSON representation might look something like
{'1':'Tuesday', '2':'Wednesday', '3':'Thursday', ... '28':'Monday', '29':'Tuesday', '30':'Wednesday'}
Coding
Write some unit tests
Phiz3 comes with its own simple unit test object called UnitTest which is in the source file UnitTest.php. Phiz3 also has it's own simple unit test library. Since this is about Phiz3's since this article is about Phiz3's Unit Tests. The there are three things you need to know to use Phiz's unit test object.
- How to create your Unit tests sub class create your own test object
- How to add your tests
- How to instantiate your test object and display results
Creating your own test object
You need to include UnitTest.php and create your own object class by extending UnitTests. You can get a current copy of UnitTest.php here. Here's an example of the code you need to write:
That's it. You've defined your own object called TestsWeekdayAPI as a child of UnitTest. Now let's add some test code. If we look at our requirements list we see that we need to parse the RESTful command line so let's start there. We're going to create a test called testRESTful. It will look at a string like /2009/09/01 and extract the year and month. We add this test as part of our TestsWeekdayAPI.
Let's look at what going in. First a bit of magic. If the method you create in your UnitTest child starts with the word 'test' it will be available to run later. Next we create an instance of our object (don't worry that we haven't written it yet, we'll do that later). We create some test data in this case We're simulating the contents of $_SERVER['PATH_INFO'] as $path_info. We setup some expected results run our method and compare.
Now that we've defined a test let's add the code create an instance of our object and run the test.
There you have it we have a basic unit test written. Now let's run it. Keep in mind it should fail sense we haven't written WeekdayAPI yet. Assuming you saved the code above as /www/TestsWeekdayAPI.php and UnitTests.php is in the same directory you could run the command php TestsWeekdayAPI.php to see the test results. You should get results looking something like this:
Fatal error: Class 'WeekdayAPI' not found in /www/TestsWeekdayAPI.php on line 5
Now we can code first part of our WeekdayAPI object. From our tests we've know we're going to pass in the restful path then return a date. Like creating unit tests we're going to sub class the Phiz3 object. Since we're haven't created the rest of our tests let's put in some place holder code.
We now can run our tests again (e.g. php TestsWeekdayAPI.php). We should get something like:
Fatal error: Class 'WeekdayAPI' not found in /www/TestsWeekdayAPI.php on line 5
Wait that's the error I got before. What I forgot to do is add the line to the top of TestsWeekdayAPI.php to include the new file WeekdayAPI.php that I've just created. Our TestsWeekdayAPI.php should now look like:
Now let's run that again (e.g. php TestsWeekdayAPI.php):
Running testRESTful Assert Failed: The method RESTful should return a date. ERROR: RESTful() not implemented. testRESTful failed. 0 assertions passed. 1 assertions failed. Summary 0 tests passed. 1 tests failed.
Now our unit test is working (i.e. no PHP errors but not yet passing). Let's go ahead and write the rest of our tests we need for WeekdayAPI. We're going to need to test building the JSON for a specific month. Finally we'll need a test for seeing if the whole assembled API is working. We can add place holder code like we did with RESTful method. When we get done we should have unit tests which run but fail for everything.
TestsWeekdayAPI.php should look something like this:
You're skeleton program should look something like this —
Running the command php TestsWeekdayAPI.php should yield something like —
Running testRESTful Assert Failed: The method RESTful should return a date. ERROR: RESTful() not implemented. testRESTful failed. 0 assertions passed. 1 assertions failed. Running testJsonForMonth Assert Failed: The jsonForMonth should return a JSON representation for Sept 2009 testJsonForMonth failed. 0 assertions passed. 1 assertions failed. Running testEventloop Assert Failed: The event loop should return our final results of the API request. testEventloop failed. 0 assertions passed. 1 assertions failed. Summary 0 tests passed. 3 tests failed.
Now you're ready to code the API itself.
Coding the API
What we've done with the unit test approach is make the problem less fuzzy and made some decisions early about how things will behave. It's not that everything is fixed in stone yet. At this stage, or even when you start coding your actual program you might find that you're assumptions about the tests were wrong. If so stop coding the solution and write some more tests. The goal of the unit test is to provide you a solid foundation, guide you on implementing your plans and verify that you've met your expectations. If you find your plans need updating (i.e. some of your base assumptions were wrong) feel free to rewrite your tests but be vigilant against two temptations - writing tests to match code you've already written and changing tests simply to have code pass. Both of those are a waste of time and effort. You want the process to flow from writing tests, testing and seeing the tests fail, writing code that passes the tests. Our API is a simple problem so we're probably pretty close our solution already and I don't expect to have to change my tests though I might want to add some more.
Let's implement our RESTful method first and get that working. You're solution might look like —
If we've built this right when running php TestsWeekdayAPI.php should yield —
Running testRESTful testRESTful OK 1 assertions passed. Running testJsonForMonth Assert Failed: The jsonForMonth should return a JSON representation for Sept 2009 testJsonForMonth failed. 0 assertions passed. 1 assertions failed. Running testEventloop Assert Failed: The event loop should return our final results of the API request. testEventloop failed. 0 assertions passed. 1 assertions failed. Summary 1 tests passed. 2 tests failed.
Now let's code jsonForMonth method —
We should have one more test passing then before —
Running testRESTful testRESTful OK 1 assertions passed. Running testJsonForMonth testJsonForMonth OK 1 assertions passed. Running testEventloop Assert Failed: The event loop should return our final results of the API request. testEventloop failed. 0 assertions passed. 1 assertions failed. Summary 2 tests passed. 1 tests failed.
And finally our eventloop might look like this —
My final test results run with php TestsWeeklyAPI.php looks like —
Running testRESTful testRESTful OK 1 assertions passed. Running testJsonForMonth testJsonForMonth OK 1 assertions passed. Running testEventloop testEventloop OK 1 assertions passed. Summary 3 tests passed. 0 tests failed.
Notice that I've decided to change what the eventloop returns. That is because we'll want to send output to the browser making the request. So I've decided that I'm returning a HTML div with any errors I've discovered rather then what boolean like I did with RESTful and jsonForMonth methods. It's not a problem because in testing for correct operations I wanted a valid JSON array and that is what I was testing for. Now we're ready to wire this up for an actual API that returns the month with days of the week as a JSON array.
Wiring up the final application
It turns out to be pretty simple. We've modeled how the API is supposed to behave all we need to do is set a content type and return results. Here's what I cam up with —
If you went to a URL where this was installed the output for /2009/09/06 would look like —
{"1":"Tuesday","2":"Wednesday","3":"Thursday", "4":"Friday","5":"Saturday","6":"Sunday", "7":"Monday","8":"Tuesday","9":"Wednesday", "10":"Thursday","11":"Friday","12":"Saturday", "13":"Sunday","14":"Monday","15":"Tuesday", "16":"Wednesday","17":"Thursday","18":"Friday", "19":"Saturday","20":"Sunday","21":"Monday", "22":"Tuesday","23":"Wednesday","24":"Thursday", "25":"Friday","26":"Saturday","27":"Sunday", "28":"Monday","29":"Tuesday","30":"Wednesday"}
Things to improve
First the error messages are pretty bad. Returning something more meaningful would be a big step in the right direction. Also error condition handling is very minimal and in a real API you'd beef that up. Our RESTful request could be simpler if we only required a year and month since the date isn't needed. You could extend the API so that requesting a specific date return that weekday name while specifying the month and year only returns the month with all the weekday labels.
I've tried to keep the unit tests minimal since this is an example. I've only check for correct conditions. You'll get better results if you also test for boundary conditions. This is particularly true as you build more complex API. This helps to prevent changing code in one place which breaks something in another place. Also to keep minimize the length of this article I've not commented my code. In practice I always try to comment at least the functions. I then can use a document generator (e.g. Doxygen) to generate source code level API docs.
Happy Coding
