Sunday 22 December 2013

Unit testing a InputFilter in Zend framework 2

Today i was working on a new resource in my api and i wanted to test my input filter so i didn't have to worry about it causing problems later when i need to do the front end.

So what is a input filter and why is it annoying to test ? Well a input filter is actually pretty much just a configuration of different elements, and testing an array structure is rather pointless. So the two things that we need to test are the element names and it's configuration.

This is my input filter that i am testing... 
/**
 * Class EstimateRequestInputFilter
 */
class EstimateRequestInputFilter extends InputFilter
{
    /**
     * {@Inheritdoc}
     */
    public function init()
    {
        $this->add(
            [
                'name'  => 'name',
                'label' => 'Name',

                'validators' => [
                    [
                        'name' => 'NotEmpty'
                    ],
                ],

                'filters' => [
                    [
                        'name' => 'StringTrim'
                    ]
                ]
            ]
        );

        $this->add(
            [
                'name'  => 'email',
                'label' => 'E-mail',

                'validators' => [
                    [
                        'name'                   => 'NotEmpty',
                        'break_chain_on_failure' => true
                    ],
                    [
                        'name' => 'EmailAddress'
                    ]
                ],

                'filters' => [
                    [
                        'name' => 'StringTrim'
                    ]
                ]
            ]
        );

        $this->add(
            [
                'name'       => 'mobile',
                'label'      => 'Mobile',
                'validators' => [
                    [
                        'name' => 'NotEmpty'
                    ]
                ],

                'filters' => [
                    [
                        'name' => 'StringTrim'
                    ]
                ]
            ]
        );

        $this->add(
            [
                'name'        => 'telephone',
                'label'       => 'Telephone',
                'allow_empty' => true,

                'filters' => [
                    [
                        'name' => 'StringTrim'
                    ]
                ]
            ]
        );

        $this->add(
            [
                'name'        => 'budget',
                'label'       => 'Budget',
                'allow_empty' => true,

                'filters' => [
                    [
                        'name' => 'StringTrim'
                    ]
                ]
            ]
        );

        $this->add(
            [
                'name'        => 'targetDate',
                'label'       => 'Target date',
                'allow_empty' => true,

                'filters' => [
                    [
                        'name' => 'StringTrim'
                    ]
                ]
            ]
        );

        $this->add(
            [
                'name'  => 'projectDescription',
                'label' => 'Project description',

                'validators' => [
                    [
                        'name'                   => 'NotEmpty',
                        'break_chain_on_failure' => true
                    ],
                    [
                        'name'    => 'StringLength',
                        'options' => [
                            'min' => 200
                        ]
                    ]
                ],

                'filters' => [
                    [
                        'name' => 'StringTrim'
                    ]
                ]
            ]
        );
    }
}

If you have a keen eye you will notice that i didn't use a constructor to define the the input filter and that is because i store all my input filters in the InputFilterPluginManager. PLEASE DO THIS!!!

For multiple reasons..
  1. It keeps the service locator clean
  2. It will inject the ValidatorManager and FilterManager so that you can access your custom validators by specifying the name you stored them as. 

And this is my testcase
class EstimateRequestInputFilterTest extends \PHPUnit_Framework_TestCase
{
    /**
     * @var \PHPUnit_Framework_MockObject_MockObject
     */
    private $validator;

    /**
     * @var \Company\InputFilter\Company\EstimateRequestInputFilter
     */
    private $inputFilter;

    protected function setUp()
    {
        $this->inputFilter = new EstimateRequestInputFilter();
        $this->inputFilter->init();
    }

    public function propertyValidationData()
    {
        return [
            ['name', '', [Validator\NotEmpty::IS_EMPTY]],  // Empty
            ['name', ' ', [Validator\NotEmpty::IS_EMPTY]], // Empty, asserting StringTrim
            ['name', 'Antoine', null, 'Antoine'],          // Valid

            ['email', '', [Validator\NotEmpty::IS_EMPTY]],              // Empty
            ['email', ' ', [Validator\NotEmpty::IS_EMPTY]],             // Empty, asserting StringTrim
            ['email', 'foo', [Validator\EmailAddress::INVALID_FORMAT]], // Invalid email address format
            ['email', 'foo@bar.com', null, 'foo@bar.com'],              // Valid

            // todo: add validation for phone numbers from i18n?
            ['mobile', '', [Validator\NotEmpty::IS_EMPTY]],
            ['mobile', ' ', [Validator\NotEmpty::IS_EMPTY]],

            ['telephone', '', null, ''],          // Valid
            ['telephone', 'test ', null, 'test'], // Valid, asserting StringTrim

            ['budget', '', null, ''],          // Valid
            ['budget', 'test ', null, 'test'], // Valid, asserting StringTrim

            ['targetDate', '', null, ''],            // Valid
            ['targetDate', 'test ', null, 'test'],   // Valid, asserting StringTrim

            ['projectDescription', '', [Validator\NotEmpty::IS_EMPTY]],  // Empty
            ['projectDescription', ' ', [Validator\NotEmpty::IS_EMPTY]], // Empty, asserting StringTrim
            ['projectDescription', 'h', [Validator\StringLength::TOO_SHORT]],
            ['projectDescription', str_repeat('h', 200), null, str_repeat('h', 200)]
        ];
    }

    /**
     * @dataProvider propertyValidationData
     *
     * @param string      $property
     * @param string      $value
     * @param array|null  $errors
     * @param string|null $expected
     *
     * @return void
     */
    public function testValidationOfEachProperty($property, $value, array $errors = null, $expected = null)
    {
        // We only validate against the given property
        $this->inputFilter->setValidationGroup([$property]);
        $this->inputFilter->setData([$property => $value]);

        // we expect errors
        if (! empty($errors)) {

            $this->assertFalse($this->inputFilter->isValid());
            $this->assertEquals($errors, array_keys($this->inputFilter->getMessages()[$property]));
        } else {

            $this->assertTrue($this->inputFilter->isValid());
            $this->assertSame($expected, $this->inputFilter->getValue($property));
        }
    }
}

So if you look closely you will see that i'm validating a number of things
  1. Validating that the element with the given name has a certain configuration
  2. Checking that the returned value matches any expected filters (In my case StringTrim)
  3. Checking that a given value with fail with a certain validator message key (Always use keys since the messages can be translated to a different language) 
P.S. This is how i do it, It is no way more correct then other methods. I just thought it would be nice to share!

Thursday 12 December 2013

Total number of results using PhlyRestfully & collections

Anybody using PhlyRestfully has most likely used a paginated result at one time, and sometimes they need to display the total number of results. It's actually rather easy but a bit different then injecting it into the result body....

What you need to do is create a listener that is attached to the SharedEventManager and inserts a header with the total count. In my case i have selected to use the header "X-total-count".

class InjectPaginationCount implements SharedListenerAggregateInterface
{
    /**
     * @var \Zend\Stdlib\CallbackHandler
     */
    protected $listener;

    /**
     * Attach one or more listeners
     *
     * Implementors may add an optional $priority argument; the SharedEventManager
     * implementation will pass this to the aggregate.
     *
     * @param SharedEventManagerInterface $events
     */
    public function attachShared(SharedEventManagerInterface $events)
    {
        $this->listener = $events->attach('PhlyRestfully\ResourceController', 'getList.post', function(Event $event) {

            /**
             * @var $response \Zend\Http\Response
             * @var $paginator \Zend\Paginator\Paginator
             */
            $response  = $event->getTarget()->getResponse();
            $paginator = $event->getParam('collection')->collection;

            if ($paginator instanceof Paginator) {
                $response->getHeaders()->addHeaderLine('X-total-count', $paginator->getTotalItemCount());
            }
        });
    }

    /**
     * Detach all previously attached listeners
     *
     * @param SharedEventManagerInterface $events
     */
    public function detachShared(SharedEventManagerInterface $events)
    {
        $events->detach('PhlyRestfully\ResourceController', $this->listener);
    }
}