PHPSpec Home Schooling: Part III – Let’s do it guys /o/

Ok, so here is what i came up with a bit of more work. It is just the tests, I think there were some basic improvements but also I notice some things could be done to shorten or simplify the size of the original phpunit test file.

<?php
 
namespace spec;
 
use PhpSpec\ObjectBehavior;
 
class PimpleSpec extends ObjectBehavior
{
    function it_is_initializable_and_implements_array_access()
    {
        $this->shouldImplement('ArrayAccess');
    }
 
    function it_stores_strings()
    {
        $this['param'] = 'value';
 
        $this['param']->shouldReturn('value');
    }
 
    function it_stores_executable_closures()
    {
        $this['service'] = function() { return new \SplObjectStorage(); };
 
        $this->offsetGet('service')->shouldHaveType('SplObjectStorage');
    }
 
    function it_returns_different_instances_from_factories()
    {
        $this['service'] = function() { return new \SplObjectStorage(); };
 
        $this->offsetGet('service')->shouldBeLike(new \SplObjectStorage());
        $this->offsetGet('service')->shouldNotBe(new \SplObjectStorage());
    }
 
    function it_executes_with_container_argument_when_value_is_a_factory()
    {
        $this['container'] = function($container) { return $container; };
 
        $this->offsetGet('container')->shouldReturn($this);
    }
 
    public function it_responds_to_isset()
    {
        $this['param'] = 'value';
        $this['service'] = function () { return new \SplObjectStorage(); };
        $this['null'] = null;
 
        $this->shouldHaveKey('param');
        $this->shouldHaveKey('service');
        $this->shouldHaveKey('null');
        $this->shouldNotHaveKey('non_existent');
    }
 
    function it_takes_key_value_pairs_via_constructor()
    {
        $this->beConstructedWith(['param' => 'value']);
        $this->offsetGet('param')->shouldReturn('value');
    }
 
    function it_tests_key_is_present()
    {
        $exception = new \InvalidArgumentException('Identifier "foo" is not defined.');
        $this->shouldThrow($exception)->duringOffsetGet('foo');
    }
 
    function it_holds_and_honors_null_values()
    {
        $this['foo'] = null;
        $this['foo']->shouldReturn(null);
    }
 
    function it_supports_unset()
    {
        $this['param'] = 'value';
        $this['service'] = function() { new \SplObjectStorage(); };
 
        unset($this['param'], $this['service']);
        $this->shouldNotHaveKey('param');
        $this->shouldNotHaveKey('service');
    }
 
    function it_shares_a_persistent_common_instance()
    {
        $this['shared_service'] = $this->share(function() { return new \SplObjectStorage(); });
        $service = $this->offsetGet('shared_service');
        $this->offsetGet('shared_service')->shouldNotBe(null);
        $this->offsetGet('shared_service')->shouldBe($service);
    }
 
    function it_can_fence_callback_from_being_factored()
    {
        $callback = function() { return 'foo'; };
 
        $this['protected'] = $this->protect($callback);
        $this->offsetGet('protected')->shouldBe($callback);
    }
 
    function it_fences_function_names_treating_them_as_parameters()
    {
        $this['global_function'] = 'strlen';
        $this->offsetGet('global_function')->shouldBe('strlen');
    }
 
    function it_gives_back_raw_value()
    {
        $this['service'] = $definition = function() { return 'foo'; };
        $this->raw('service')->shouldBe($definition);
    }
 
    function it_gives_back_raw_nulls()
    {
        $this['foo'] = null;
        $this->raw('foo')->shouldBe(null);
    }
 
    function it_tests_key_present_on_raw_call()
    {
        $exception = new \InvalidArgumentException('Identifier "foo" is not defined.');
        $this->shouldThrow($exception)->duringRaw('foo');
    }
 
    function it_can_extend_values()
    {
        $this['shared_service'] = $this->share(function() { return new \SplObjectStorage(); });
        $value = 123;
        $assignmentDecorator = function($sharedService) use ($value) {
            $sharedService->value = $value;
 
            return $sharedService;
        };
        $this->extend('shared_service', $assignmentDecorator);
 
        $this->offsetGet('shared_service')->shouldHaveType(get_class($this->offsetGet('shared_service')));
        $this->offsetGet('shared_service')->value->shouldBe($value);
        $this->offsetGet('shared_service')->value->shouldBe($this->offsetGet('shared_service')->value);
        $this->offsetGet('shared_service')->shouldBe($this->offsetGet('shared_service'));
    }
 
    function it_can_list_all_keys_present()
    {
        $this['foo'] = 5;
        $this['bar'] = 7;
        $this->keys()->shouldReturn(['foo', 'bar']);
    }
 
    function it_checks_for_valid_key_when_extending()
    {
        $exception = new \InvalidArgumentException('Identifier "foo" is not defined.');
        $this->shouldThrow($exception)->duringExtend('foo', function() {});
    }
 
    function it_invokes_factory_when_value_is_an_invokable_object()
    {
        $this['invokable'] = new Invokable();
        $this->offsetGet('invokable')->shouldReturn('I was invoked');
    }
 
    function it_treats_non_invokable_object_as_a_parameter()
    {
        $objectParameter = new \StdClass();
        $this['non_invokable'] = $objectParameter;
        $this->offsetGet('non_invokable')->shouldReturn($objectParameter);
    }
}
 
class Invokable
{
    function __invoke()
    {
        return 'I was invoked';
    }
}

Things to notice:

– The tests involving special functional methods are the hardest to implement upon given specification.
– If we cluster all setup into a let() method this class could go downto few lines but may not be a good idea
– The final size is 174 lines compared to the 270 lines of phpunit. An extra of 270 – 174 = 96 lines!!
– The clarity of the code is amazing and the method naming is perfect and clear
– The shortening of line numbers would be even more dramatic should we need to do more mocking
– Notice Pimple is probably one of the smallest cases you would find of something very powerful and useful
– Now we can refactor without fear and experiment with Pimple and why it was build the way it was built
– Now we can understand which functionality was added with time to support the Silex framework

I don’t want to cheat more and copy the implementations of the hard methods since this involves functional thinking and better implementations could come, so I will keep working on different implementations.
This Spec can be handed out and we can start creating a PimpleInterface so that even we, yes we, can create our own di container called ‘Simple’ :), very simple right?

I even sent this PR https://github.com/fabpot/Pimple/pull/76

Very glad I am enjoying this graceful and totally undeserved learning :)! I hope you are too!

John ~ Revelation 22:
“6 The angel said to me, “These words are trustworthy and true. The Lord, the God who inspires the prophets, sent his angel to show his servants the things that must soon take place.”
7 “Look, I am coming soon! Blessed is the one who keeps the words of the prophecy written in this scroll.”
8 I, John, am the one who heard and saw these things. And when I had heard and seen them, I fell down to worship at the feet of the angel who had been showing them to me. 9 But he said to me, “Don’t do that! I am a fellow servant with you and with your fellow prophets and with all who keep the words of this scroll. Worship God!””

One thought on “PHPSpec Home Schooling: Part III – Let’s do it guys /o/

  1. Pingback: Porting your PHPUnit legacy test suite to PhpSpec Adventures: Part VI | Craft It Online!

Leave a Reply

Your email address will not be published. Required fields are marked *