Etsy sync WordPress plugin 07

A peek into my TDD flow while developing the Etsy WordPress plugin.

Not the only and not the best way

I’ve not enough coding experience and written code to make any pretences over a proper “tdd flow”: this is just the way I do it and since this blog is a diary to me rather than a “this is the proper way” manual I will gladly share my mistakes, ignorances, short-sightedness and short comings and that’s the spirit anything I write should be read.

Interruption

I’m moving forward in the development of the Etsy WordPress plugin and taking my time to “do things right” and in a TDD fashion.
While TDDint the ELH_RequestCompiler class I had to stop on my tracks and move to the development of another class.

use tad\FunctionMocker\FunctionMocker as Test;

class ELH_RequestCompilerTest extends \PHPUnit_Framework_TestCase {

    protected function setUp() {
        Test::setUp();
    }

    protected function tearDown() {
        Test::tearDown();
    }

    //more tests here...

    /**
     * @test
     * it should throw an exception if a required parameter has the wrong type
     */
    public function it_should_throw_an_exception_if_a_required_parameter_has_the_wrong_type() {
        $this->setExpectedException( 'ELH_RequestParameterException' );

        $sut     = new ELH_RequestCompiler();
        $params  = [
            new ELH_ApiRequestParameter( 'foo', true, null, 'string' ),
            new ELH_ApiRequestParameter( 'baz', false, 10, 'int' ),
            new ELH_ApiRequestParameter( 'bar', false, 'none', 'string' )
        ];
        $request = Test::replace( 'ELH_ApiRequestInterface' )->method( 'parameters', $params )
                       ->method( 'uri', '/listings/:foo/shops' )->get();
        $sut->set_request( $request );

        $data = [ 'foo' => 23, 'bar' => 'woo' ];
        $out  = $sut->get_compiled_request( $data );
    }
}

I will be using Exceptions and their catching massively in the project and am making sure here one is thrown if the class user (probably me, but “never trust the user”), tries to pass a value for a request parameter that’s not of the proper type: here it’s the foo parameter that’s supposed to have a string type and is being passed a value of 23.
In the ELH_RequestCompiler class code I began writing a method to check the type and then stopped on my tracks: that’s too granular a concern for the class to handle and should be moved to another class. The ELH_RequestCompiler has the following code at this moment

class ELH_RequestCompiler {

    /**
     * @var ELH_ApiRequestInterface
     */
    protected $request;

    public function set_request( ELH_ApiRequestInterface $request ) {
        $this->request = $request;
    }

    public function get_compiled_request( array $data = array() ) {
        $compiled                    = $this->request->uri();
        $required_and_default_values = $this->get_required_and_defaults();
        $data                        = array_merge( $required_and_default_values, $data );

        $this->check_parameters( $data );

        $vars = array();
        foreach ( $data as $key => $value ) {
            if ( strpos( $compiled, ':' . $key ) ) {

                $compiled = str_replace( ':' . $key, $value, $compiled );
            } else {
                $vars[] = sprintf( '%s=%s', $key, $value );
            }
        }

        $compiled .= '?' . implode( '&', $vars );

        return $compiled;
    }

    private function get_required_and_defaults() {
        $list = array();

        foreach ( $this->request->parameters() as $parameter ) {
            if ( $parameter->is_required() && ! is_null( $parameter->default_value() ) ) {
                $list[ $parameter->name() ] = $parameter->default_value();
            }
        }

        return $list;
    }

    private function check_parameters( array $data ) {
        foreach ( $this->request->parameters() as $parameter ) {
            $this->check_parameter_value( $data, $parameter );
            if ( isset( $data[ $parameter->name() ] ) ) {
                $value = $data[ $parameter->name() ];
                $this->check_parameter_type( $value, $parameter );
            }
        }
    }

    /**
     * @param array $data
     * @param       $parameter
     *
     * @throws ELH_RequestParameterException
     */
    private function check_parameter_value( array $data, $parameter ) {
        if ( $parameter->is_required() && ! array_key_exists( $parameter->name(), $data ) ) {
            throw new ELH_RequestParameterException( sprintf( 'Required parameter %s has no value.', $parameter->name() ) );
        }
    }

    /**
     * @param $value
     * @param $parameter
     *
     * @throws ELH_RequestParameterException
     */
    private function check_parameter_type( $value, $parameter ) {
        if ( ! ELH_TypeUtils::is_a( $value, $parameter->type() ) ) {
            throw new ELH_RequestParameterException( sprintf( 'Parameter %s must be of type %s.', $parameter->name(), $parameter->type() ) );
        }
    }
}

and I’ve decided I will move the type check made in the check_parameter_type method to the ELH_TypeUtils class and have that deal with the problem. I’ve stubbed that class to a minimum

class ELH_TypeUtils {

    public static function is_a( $value, $type ) {
        return true;
    }
} 

But right now my tests are failing. ELH_RequestCompiler failing tests

Stubbing the static method

Before moving the the development of the ELH_TypeUtils class I will make sure all of the tests for the ELH_RequestCompiler are passing, to have that happening I will simply replace the result of the ELH_TypeUtils::is_a method to return true or false as I need it to.

use tad\FunctionMocker\FunctionMocker as Test;

class ELH_RequestCompilerTest extends \PHPUnit_Framework_TestCase {

    protected function setUp() {
        Test::setUp();
    }

    protected function tearDown() {
        Test::tearDown();
    }

    /**
     * @test
     * it should throw an exception if a required parameter has the wrong type
     */
    public function it_should_throw_an_exception_if_a_required_parameter_has_the_wrong_type() {
        $this->setExpectedException( 'ELH_RequestParameterException' );

        Test::replace('ELH_TypeUtils::is_a', false);

        $sut     = new ELH_RequestCompiler();
        $params  = [
            new ELH_ApiRequestParameter( 'foo', true, null, 'string' ),
            new ELH_ApiRequestParameter( 'baz', false, 10, 'int' ),
            new ELH_ApiRequestParameter( 'bar', false, 'none', 'string' )
        ];
        $request = Test::replace( 'ELH_ApiRequestInterface' )->method( 'parameters', $params )
                       ->method( 'uri', '/listings/:foo/shops' )->get();
        $sut->set_request( $request );

        $data = [ 'foo' => 23, 'bar' => 'woo' ];
        $out  = $sut->get_compiled_request( $data );
    }
}

I’ve modified the last test method and used function-mocker static method replacing capabilities to have the result I need returned. Now that I know the ELH_RequestCompiler is working to this point I will move into the development of the ELH_TypeUtils class. ELH_RequestCompiler passing tests

Testing the ELH_TypeUtils class

The class is really just a function name-spacing in PHP 5.2 land but nonetheless has some logic running and requires, as such, some testing; skipping forward to the end of the cycle some testing and development later I’ve ended up with this test code

use tad\FunctionMocker\FunctionMocker as Test;

class ELH_TypeUtilsTest extends \PHPUnit_Framework_TestCase {

    protected function setUp() {
        Test::setUp();
    }

    protected function tearDown() {
        Test::tearDown();
    }

    public function properValueTypePairs() {
        return [
            [ 'foo', 'string' ],
            [ 23, 'int' ],
            [ '23', 'int' ],
            [ 1.5, 'float' ],
            [ '1.5', 'float' ]
        ];
    }

    /**
     * @test
     * it should return true for proper value and type pairs
     * @dataProvider properValueTypePairs
     */
    public function it_should_return_true_for_proper_value_and_type_pairs( $value, $type ) {
        Test::assertTrue( ELH_TypeUtils::is_a( $value, $type ) );
    }

    public function notProperValueTypePairs() {
        return [
            [ 23, 'string' ],
            [ 1.5, 'string' ],
            [ 'foo', 'int' ],
            [ 1.5, 'int' ],
            [ '1.5', 'int' ],
            [ 23, 'float' ],
            [ '23', 'float' ],
            [ 'foo', 'float' ]
        ];

    }

    /**
     * @test
     * it should return false for not proper value and type pairs
     * @dataProvider notProperValueTypePairs
     */
    public function it_should_return_false_for_not_proper_value_and_type_pairs( $value, $type ) {
        Test::assertFalse( ELH_TypeUtils::is_a( $value, $type ) );
    }

    public function orProperValueTypePairs() {
        return [
            [ 'foo', 'string|int' ],
            [ '12', 'string|int' ],
            [ 23, 'string|int' ],
            [ 23, 'int|float' ],
            [ '23', 'int|float' ],
            [ '23.23', 'int|float' ],
            [ 23.23, 'int|float' ],
            [ 'foo', 'float|string' ],
            [ '23.23', 'float|string' ],
            [ 23.23, 'float|string' ],
            [ 'foo', 'string|int|float' ],
            [ 23, 'string|int|float' ],
            [ '23', 'string|int|float' ],
            [ 1.5, 'string|int|float' ],
            [ '1.5', 'string|int|float' ]
        ];
    }

    /**
     * @test
     * it should allow specifying more than one allowed type
     * @dataProvider orProperValueTypePairs
     */
    public function it_should_allow_specifying_more_than_one_allowed_type( $value, $type ) {
        Test::assertTrue( ELH_TypeUtils::is_a( $value, $type ) );
    }

    public function nonScalarValues() {
        return [
            [ array( 23 ) ],
            [ new stdClass() ]
        ];

    }

    /**
     * @test
     * it should return false for non scalar values
     * @dataProvider nonScalarValues
     */
    public function it_should_return_false_for_non_scalar_values( $value ) {
        Test::assertFalse( ELH_TypeUtils::is_a( $value, 'not relevant' ) );
    }

    /**
     * @test
     * it should fail on non valid types
     */
    public function it_should_fail_on_non_valid_types() {
        Test::assertFalse( ELH_TypeUtils::is_a( 'foo', 'not a type' ) );
    }
}

to develop the current incarnation of the ELH_TypeUtils class

class ELH_TypeUtils {

    public static function is_a( $value, $type ) {
        if ( ! is_scalar( $value ) ) {
            return false;
        }

        $types = strpos( $type, '|' ) ? explode( '|', $type ) : array( $type );
        $check = false;
        foreach ( $types as $_type ) {
            switch ( $_type ) {
                case 'string':
                    $check = $check || is_string( $value );
                    break;
                case 'int':
                    $check = $check || ( is_numeric( $value ) && intval( $value ) == floatval( $value ) );
                    break;
                case 'float':
                    $check = $check || ( is_numeric( $value ) && intval( $value ) != floatval( $value ) );
                    break;
                default:
                    $check = false;
                    break;
            }
        }

        return $check;
    }
} 

Next

I will finish the ELH_RequestCompiler class and begin some sync testing.