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!