Okay, much sooner than expected, I found a good candidate for some more complex unit testing: the VuFindCart 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 VuFindCart, 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.

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 VuFindCart, 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, VuFindCart depends on VuFindRecordLoader. 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 VuFindCart interacts with VuFindRecordLoader, 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 VuFindCart 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 VuFindCart 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.

Conclusions

I hope this further demonstrates how testing can be achieved. As before, please feel free to ask questions!

As of this morning, I have finished up the cart tests to reach 100% code coverage. You can see the additions here.

A couple of minor notes:

1.) As mentioned in the article above, I used the mock testGetRecordDetails() object to create a test that checks if the Cart properly interacts with the Record Loader. We want to make sure that the cart sends the correct values into the loader and returns whatever the loader sends back. We test the first part with an expects() condition in the mock, and we test the second part by configuring the mock to return a specific value and checking for that value with an ->assertEquals() assertion.

2.) There is one line of code in the cart class that we can’t test — the setcookie() line; it’s inherently untestable from the command-line environment. We’ll just have to live with that. Fortunately, PHPUnit allows annotation comments to exclude untestable lines from code coverage reports — by surrounding the offending code with // @codeCoverageIgnoreStart and // @codeCoverageIgnoreEnd we can mark the problem area.