views:

152

answers:

1

The short question: Is there a way to reset a Mock object in SimpleTest, removing all expectations?

The longer explanation:

I have a class that I'm testing using SimpleTest and am having some problem with the Mock objects it is using.

The class is a Logger, and inside the logger are a number of Writer objects (FileWriter, EmailWriter, etc). Calling the Logger::log() method performs some logic behind the scenes and routes the message to the correct writer. Writers are cached in the Logger class to save re-instantiating each one each time.

In my unit tests, I set up a Logger, create and add some Mock Writer objects to it and then have been using methods like MockDBWriter->expectOnce() to test that the Logger is working.

The problem now is that I want to test another function of the Logger, but the expectOnce expectations are still in effect and causing my subsequent tests to fail.

function testWritesMessageOK() {
    $log = Logger::getInstance();
    $mock = new MockFileWriter($this);

    $log->addWriter($mock);

    $mock->expectOnce("write", "Message");

    $log->write("Message");    // OK
}

// this is just an example - the actual test is much less inane
function testNumberOfWrites() {
    $log = Logger::getInstance();
    $mock = $log->getWriter();

    $mock->expectCallCount('write', 2);

    $log->write("One");    // fail - it doesn't match "Message"
    $log->write("Two");
}

Is there a way to reset a Mock object, removing all expectations?

+2  A: 

Use separate mock instances.

Either:

$mock = $log->getWriter();
$mock = new $mock;

Or:

$mock = new MockFileWriter($this);
// And then:
$mock = new MockDBWriter($this);
// And then:
$mock = new MockEmailWriter($this);
// etc.

I'd question the wisdom of caching writers to save re-instantiation. If you make instantiation a cheap operation (i.e. don't create DB connection or anything) and defer that sort of thing until you actually need the connection, such as the first query, then you won't need to cache and this whole problem might go away.

The other thing you can do is call the SimpleMock constructor.

$mock = $log->getWriter();
$mock->SimpleMock();

Which will do all this:

/**
 *    Creates an empty action list and expectation list.
 *    All call counts are set to zero.
 *    @access public
 */
function SimpleMock() {
    $this->_actions = &new SimpleCallSchedule();
    $this->_expectations = &new SimpleCallSchedule();
    $this->_call_counts = array();
    $this->_expected_counts = array();
    $this->_max_counts = array();
    $this->_expected_args = array();
    $this->_expected_args_at = array();
    $test = &$this->_getCurrentTestCase();
    $test->tell($this);
}

The only problem with that is that tell() call at the end which will cause the SimpleMock::atTestEnd() to be called twice when tallying up the expectations. But, you could fix that with this:

// $this should == the test case in question
array_pop($this->_observers);

This answer is based on version 1.0.1 of SimpleTest.

Ollie Saunders