Page 1 of 1

Unit testing serialization

Posted: Wed May 23, 2007 11:37 pm
by Ambush Commander
Hello folks, I've got a sticky unit testing situation that I'd like to resolve. I have created a class that accepts objects to be cached, which it does by serializing the object and writing it to a file.

I don't mind the file writing operation: it's quite safe and there's virtually no chance of collision. However, I'm having difficulty determining whether or not to mock the object that is going to be cached (the one that will be serialized). If I mock it, I'll end up serializing a whole kaboodle of private testing machinery that may or may not be shifting around. If I don't mock it, I have no way of stopping it from calling other dependencies... i.e. it's not a unit test anymore (for an integration test, this is fine, but I'm trying to TDD).

Perhaps the serialization has to be abstracted out and mocked, but that seems rather extreme just for a unit test. It's a single function, no bells and whistles.

Posted: Thu May 24, 2007 2:57 am
by Maugrim_The_Reaper
I don't think you should mock the object being serialised and cached. In a sense the object is itself test data so it should be left alone. I usually just use a stdClass instance. If you need anything more it's more likely you'd end up testing serialize() rather than the class calling serialize(). Does the class commit itself to a specific object type?

Posted: Thu May 24, 2007 4:02 am
by Jenk
create an intercept so you don't have to actually call serialize to test it. Something like comparing the cache stack (array)

Code: Select all

class TestCache extends UnitTestCase
{
    public function __construct()
    {
        parent::__construct();
    }

    public function testCacheStack ()
    {
        $cache = new CacheObject();
        $objectToBeCached = new stdClass;
        $cache->prepareForCache($objectToBeCached);
        $this->assertEqual($cache->getStack(), array($objectToBeCached));
    }
    // or compare strings
    public function testCacheString ()
    {
        $cache = new CacheObject();
        $objectToBeCached = new stdClass;
        $cache->prepareForCache($objectToBeCached);
        $this->assertEqual($cache->getSerialized(), serialize(array($objectToBeCached)));
    }        
}

Similar sort of thing when testing a DB layer - you can test what the final statement is without needing to call the respective DB function.

Posted: Thu May 24, 2007 5:00 am
by Maugrim_The_Reaper
I would definitely call serialize(). It's an internal PHP function not a datasource which is supposed to consistently operate on objects. One day PHP might alter that function and I'd prefer if my Unit Tests picked it up should that happen. It's a significant class specific behaviour - avoiding a direct test would appear to hamstring the test by focusing solely on the caching aspect.

Posted: Thu May 24, 2007 5:55 am
by Jenk
Both tests are valid, imo. You are testing that the stack operation is correct as well as the serialization.

Posted: Thu May 24, 2007 2:45 pm
by Ambush Commander
Quick note: PHP 4.

stdClass almost works. I'm not enforcing a specific type, so I can get away with putting something random in there. However, before being serialized, I need the object do some __sleep() style functionality. Since I'm on PHP 4, the serializer/cache class has to do this work on the object. But what this means is that methods are going to be called, and an stdClass will fatally error out.

I imagine there are two ways out of this dilemma:

1. Create a custom mock that implements the necessary interface, and destroys the reference to the test case once its done.

2. Get rid of the __sleep() style functionality (as of right now, I'd only need this sort of thing to ensure that the definition object was properly initialized, which I could make sure always happens with a little discipline)

I'm probably going to feel this out myself, but creating an intercept seems (to me, at least) a little over the top, since no processing is happening to the object: it's just being serialized straight. What would you do?

Posted: Thu May 24, 2007 4:29 pm
by Jenk
just mock the __sleep() method.

Posted: Thu May 24, 2007 4:31 pm
by Ambush Commander
I'll take that as an endorsement of the first approach.

Posted: Thu May 24, 2007 6:12 pm
by Jenk
no, I mean literally mock the __sleep() method..

Code: Select all

class ObjectToBeMocked //dummy definition for mock creation
{
    function __sleep() {}
}

Mock::generate('ObjectToBeMocked', 'MockObject');

class TestBlah extends UnitTestCase
{
    function TestBlah ()
    {
        $this->UnitTestCase();
    }

    function testMockCache ()
    {
        $cache =& new Cache();
        $mock =& new MockObject($this);
        $mock->expectOnce('__sleep');
        $cache->cache($mock);
    }
}

Posted: Thu May 24, 2007 6:52 pm
by Ambush Commander
But then I'll end up serializing all that mock class machinery and stuff (and who knows, maybe some recursive references, which wouldn't be good).

Posted: Thu May 24, 2007 7:48 pm
by Jenk
Why wouldn't it be good? :?

You're testing.. not running live.

Posted: Thu May 24, 2007 7:50 pm
by Ambush Commander
It's mostly because I've var_dump'ed a mock object before accidentally. They're not pretty.

Posted: Thu May 24, 2007 8:17 pm
by Jenk
Quite, but you don't have to manually type it all out; all you are looking to do is test that your object is performing as expected - i.e. it is serializing a given parameter and writing it to a file. You're not the one who is actually serailizing, that's down to PHP's serialize function, so you can do:

Code: Select all

$this->assertEqual(file_get_contents($theCacheFile), serialize($theObjectBeingSerialized));