Okay, much sooner than expected, I found a good candidate for some more complex unit testing: the VuFind\Cart object that we mocked in the previous post.
Preparing for Testing
As I mentioned in the previous post, sometimes a bit of work needs to be done before classes can be easily tested.
In the case of VuFind\Cart, the class reads and writes cookie values (since this is how VuFind tracks book bag contents). This is problematic for testing — the class is dependent on web-specific elements in the environment. The solution is easy enough, though: add some extra abstraction.
First, we add the ability to override $_COOKIE through an extra constructor parameter: diff.
Next, we create a wrapper function around setcookie(): diff.
Now we can test everything easily even if we don’t have real cookies. This abstraction may also come in handy in the future if Zend Framework offers its own cookie handling utilities, since we have reduced the number of times that VuFind directly utilizes low-level PHP functionality.
Writing the Test
As of this writing, we don’t have 100% test coverage for VuFind\Cart, but the work-in-progress test demonstrates a few useful techniques. You might want to open the full code in another tab so you can look at it as you read the notes below.
First of all, as discussed in the previous post, VuFind\Cart depends on VuFind\Record\Loader. I use a mock object to satisfy that dependency. The mock object is created as a property of the test class — this way, if tests need to make assertions about how VuFind\Cart interacts with VuFind\Record\Loader, these can easily be set up. (I haven’t done this yet, but it will be necessary to get 100% coverage).
I have created a getCart() convenience method which returns a VuFind\Cart mock object. This routine uses the second parameter of getMock(), which can be used to specify which methods are to be mocked out. In this case, we are only mocking out the setCookie() wrapper function, since we don’t want our test to actually call PHP’s setcookie() function. Everything else will be executing real code from the VuFind\Cart class.
Most of the actual test cases are simple low-hanging fruit — setting values in the constructor and making sure that the corresponding get methods return them correctly, etc. It should be fairly self explanatory.
The testFullCart() method might be of some interest — this demonstrates using a series of assertions to confirm that an expected sequence of events happens in the appropriate order (in this case, we are making sure that the cart registers as being full at the right point in time).
The testCookieWrite() method further demonstrates the power of mock objects. We want to test that adding an item to the cart writes the correct values to the cookie. Since we have mocked out the setCookie() method, we can set up expectations that it will be called at particular times with particular values — this allows us to test that a call to $cart->addItem() results in the expected calls to $cart->setCookie(), without actually writing any real cookies. The PHPUnit syntax can seem a bit weird at first, but it’s not really too bad. In this example, $this->at(0) and $this->at(1) are being used to set up rules relating to the first and second calls to $cart->setCookie; the ->with() portion sets up expectations for particular parameters at each call.
I hope this further demonstrates how testing can be achieved. As before, please feel free to ask questions!