Take the 2-minute tour ×
Magento Stack Exchange is a question and answer site for users of the Magento e-Commerce platform. It's 100% free, no registration required.

I want to test if the following method writes ( or deletes ) the data in the expected way:

Method:

protected function saveInstanceRelations( Mage_Core_Model_Abstract $object )
{

    $oldInstances = $this->lookupRelationIds( $object->getId(), $this->getTable('slider/slide_instance'), 'instance_id' );
    $newInstances = (array) $object->getInstances();

    $table  = $this->getTable('slider/slide_instance');
    $insert = array_diff($newInstances, $oldInstances);
    $delete = array_diff($oldInstances, $newInstances);

    if ( $delete )
    {
        $where = array(
            'slide_id = ?'     => (int) $object->getId(),
            'instance_id IN (?)' => $delete
        );

        $this->_getWriteAdapter()->delete($table, $where);
    }

    if ( $insert )
    {
        $data = array();

        foreach ($insert as $instanceId) {
            $data[] = array(
                'slide_id'  => (int) $object->getId(),
                'instance_id' => (int) $instanceId
            );
        }

        $this->_getWriteAdapter()->insertMultiple($table, $data);
    }
}

Test for that method:

/**
 * @test
 * @loadFixture
 * @doNotIndexAll
 */
public function testSaveInstanceRelations()
{

    $mock = $this->getMock('Mage_Core_Model_Abstract');
    $mock->expects($this->any())
        ->method('getId')
        ->will( $this->returnValue( 1 ));

    $mock->expects($this->any())
        ->method('__call')
        ->with('getInstances')
        ->will( $this->returnValue( array(1,2)));

    EcomDev_Utils_Reflection::invokeRestrictedMethod(
        Mage::getResourceModel('slider/slides'),
        'saveInstanceRelations',
        array( $mock )
    );

I could write a simple db-query to check if the data gets written correct, but i don`t think this is a clever way to do that.

share|improve this question
add comment

3 Answers 3

up vote 3 down vote accepted

Unittests tests unit, everything else is mocked, especially web, http and databases. Therefore you should mock _getWriteAdapter and check, if the sql query you expect is the right one.

To be more precise, check what the arguments of delete($table, $where); are.

That's it. If you find later a bug, add a new failing test, with the problematic parameters, then change your code to cover this test too.

share|improve this answer
add comment

Many possible ways to do this.

As suggested by Fabian, mocking out the write adapter and then checking that delete() is called on it with the expected parameters is one possibility.
However, I find mocking of db adapters always very complex when methods are not fully isolated from all dependencies (as in this case).

I would suggest refactoring the method a little to make it more testable.

For one, you might want to pass in the $oldInstances array, instead of having to call the method inside saveInstanceRelations(). That way you don't have to worry about mocking the read result when testing.

Another way to quickly test would be to use intermediate data structures, meaning you could create a method that simply returns the IDs to insert, and another one to return the IDs to delete, and then write tests if they do what they should.

That way you could isolate the logic to create the arrays from the logic that does the DB update, hence giving you simpler tests.

Finally, since you are using EcomDev_PHPUnit anyway, you could choose to write an integration test (vs. a classic unit test) by specifying DB fixtures, calling the method, and checking the actuall results in the database afterwards.
That way you wouldn't need to mock anything (at the cost of a slower test).

All approaches are valid IMHO, its a matter of how much time you want to invest to write the test and how fast you want the tests to run.

share|improve this answer
add comment

Finally i got it!

I moved insertMultiple() and delete() to a separate function and changed my test to this:

public function testSaveInstanceRelations()
{
    // Mage_Core_Model_Abstract
    $coreAbstractModelMock = $this->getModelMock('core/abstract', array('getId', '__call'), true);
    $coreAbstractModelMock->expects($this->any())
        ->method('getId')
        ->will( $this->returnValue( 1 ));

    $coreAbstractModelMock->expects($this->any())
        ->method('__call')
        ->with('getInstances')
        ->will( $this->returnValue( array( 1, 2 ) ) );

    // Skeleton_Slider_Model_Resource_Slides
    $sliderSlidesStub = $this->getResourceModelMock('slider/slides', array('lookupRelationIds', '_insertMultiple', '_delete') );
    $sliderSlidesStub->expects($this->once())
        ->method('lookupRelationIds')
        ->will( $this->returnValue( array( 1, 3 ) ) );

    $sliderSlidesStub->expects($this->once())
        ->method('_insertMultiple')
        ->with(
            $this->equalTo('slider_slide_instance'),
            $this->equalTo( array(
                array(
                    'slide_id' => 1,
                    'instance_id' => 2
                )
            ))
        );

    $sliderSlidesStub->expects($this->once())
        ->method('_delete')
        ->with(
            $this->equalTo('slider_slide_instance'),
            $this->equalTo( array(
                'slide_id = ?' => 1,
                'instance_id IN (?)' => array(
                    1 => 3
                )
            ))
        );

    $this->replaceByMock('resource_model', 'slider/slides', $sliderSlidesStub );

    EcomDev_Utils_Reflection::invokeRestrictedMethod(
        Mage::getResourceModel('slider/slides'),
        'saveInstanceRelations',
        array( $coreAbstractModelMock )
    );

}

Thanks to @Vinai and @Fabian Blechschmidt

share|improve this answer
add comment

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.