Developing a plugin using DI and TDD 03

Thought overkill over the first tests and still nothing.

What’s to test?

I left the idlikethis_Shortcodes_Simple class code in a working state but not doing nothing and did that to stop and think a little about it and write the first test.
What should be tested is a matter of personal state in the beginning and of sheer understanding of scope and relations in the long run.
At first I might think “I have to make sure the class responsible for returning shortcode markup should return said markup”.
Fine: how to test it? Or, how to test it now and make sure the class and the tests will honor the single responsibility principle in its “a class should have only one reason to change” declination?
Since I’ve previously defined the class responsibility it’s easy to understand what should be tested:

  • not the shortcode markup: it might, and probably will, change in future iterations and the class single responsibility would not have changed
  • not the class reliance on the Smarty template engine: the rendering engine of choice might change in future iterations
  • not the class WordPress game: hooking is taken care of in the Shortcodes service provider

During the Forum PHP conference a talented speaker presented a speech about how tests are a way to project object relations and communication rather than implementations. I approve of this and sticking to it I’ve modified the class code to this

<?php class idlikethis_Shortcodes_Simple implements idlikethis_Shortcodes_ShortcodeInterface { /** * @var idlikethis_Templates_RenderEngineInterface */ private $render_engine; /** * @param idlikethis_Templates_RenderEngineInterface $render_engine */ public function __construct(idlikethis_Templates_RenderEngineInterface $render_engine) { $this->render_engine = $render_engine; } /** * Returns the shortcode tag. * * @return string */ public function get_tag() { return 'idlikethis'; } /** * Returns the shortcode rendered markup code. * * @return string */ public function render() { return "<button>I'd like this</button>"; } } 

In essence I’m not type-hinting the Smarty class any more but have moved to an interface, RenderEngine, that any rendering engine adapter the plugin will use will take care of implementing.
The service provider has in turn changed to accommodate for that

<?php class idlikethis_ServiceProviders_Shortcodes extends tad_DI52_ServiceProvider { /** * Binds and sets up implementations. */ public function register() { $this->container->singleton('idlikethis_Templates_RenderEngineInterface', 'idlikethis_Adapters_SmartyAdapter'); $this->container->bind('idlikethis_Shortcodes_ShortcodeInterface', 'idlikethis_Shortcodes_Simple'); $simple_shortcode = $this->container->resolve('idlikethis_Shortcodes_ShortcodeInterface'); add_shortcode($simple_shortcode->get_tag(), array($simple_shortcode, 'render')); } /** * Binds and sets up implementations at boot time. */ public function boot() { // TODO: Implement boot() method. } } 

Since I’m not relying on the rendering engine to be exactly Smarty anymore I’ve added an adapter class that will in turn implement the idlikethis_Templates_RenderEngineInterface interface.
To have the test code run I just need the class scaffolded and not fully implemented so here it is

<?php class idlikethis_Adapters_SmartyAdapter implements idlikethis_Templates_RenderEngineInterface { /** * Renders a template using the provided data. * * @param string $template_slug * @param array $data */ public function render($template_slug, array $data = array()){ // implement me } } 

Again, I know this sounds overkill but what I like about TDD is that it forces me to think in terms of iterations, changes and in general forces a more planned approach to code.

Testing the simple shortcode class

Now that I’ve put the pieces in place it’s time to write the first tests; Codeception and the wp-browser add-on allow for quick scaffolding of a WordPress unit test

wpcept generate:wpunit wpunit "idlikethis\Shortcodes\Simple" 

In plain English it means

Hey wpcept (wpbrowser add-on CLI tool), add a WordPress unit test in the ‘wpunit’ suite for the ‘Simple’ shortcode class.

After that it’s a matter of filling out the blanks going through the TDD red light, green light cycle and after some iterations here it’s the test class code

<?php namespace idlikethis\Shortcodes; use idlikethis_Shortcodes_Simple as Simple; class SimpleTest extends \Codeception\TestCase\WPTestCase { /** * idlikethis_Templates_RenderEngineInterface */ protected $render_engine; public function setUp() { // before parent::setUp(); // your set up methods here $this->render_engine = $this->prophesize('idlikethis_Templates_RenderEngineInterface'); } public function tearDown() { // your tear down methods here // then parent::tearDown(); } protected function make_instance() { return new Simple($this->render_engine->reveal()); } /** * @test * it should be instantiatable */ public function it_should_be_instantiatable() { $sut = $this->make_instance(); $this->assertInstanceOf('idlikethis_Shortcodes_Simple', $sut); } /** * @test * it should call the render engine with data when rendering */ public function it_should_call_the_render_engine_with_data_when_rendering() { $sut = $this->make_instance(); $sut->set_template_slug('some/slug'); $data = array('some' => 'value', 'some_other' => 'value'); $sut->set_template_data($data); $this->render_engine->render('some/slug', $data)->shouldBeCalled(); $sut->render(); } /** * @test * it should throw if trying to set template slug to non string */ public function it_should_throw_if_trying_to_set_template_slug_to_non_string() { $sut = $this->make_instance(); $this->setExpectedException('InvalidArgumentException'); $sut->set_template_slug(23); } /** * @test * it should throw if trying to set the template slug to empty string */ public function it_should_throw_if_trying_to_set_the_template_slug_to_empty_string() { $sut = $this->make_instance(); $this->setExpectedException('InvalidArgumentException'); $sut->set_template_slug(''); } } 

What’s missing? The test for the defaults template_slug and template_data values, why? Because those are a dependency (a primitive one) of the shortcode class and might change in the future.
Testing the class exposes the class communications and relations or their lack: in this case I’ve added the non intuitive methods to set the template_slug and template_data fields.

<?php class idlikethis_Shortcodes_Simple implements idlikethis_Shortcodes_ShortcodeInterface { /** * @var string */ protected $template_slug; /** * @var array */ protected $template_data; /** * @var idlikethis_Templates_RenderEngineInterface */ protected $render_engine; /** * @param idlikethis_Templates_RenderEngineInterface $render_engine */ public function __construct(idlikethis_Templates_RenderEngineInterface $render_engine) { $this->render_engine = $render_engine; $this->template_slug = 'shortcodes/slug'; $this->template_data = array( 'text' => __("I'd like this", 'idlikethis'), ); } /** * Returns the shortcode tag. * * @return string */ public function get_tag() { return 'idlikethis'; } /** * Returns the shortcode rendered markup code. * * @return string */ public function render() { return $this->render_engine->render($this->template_slug, $this->template_data); } /** * @param $template_slug */ public function set_template_slug($template_slug) { if (!is_string($template_slug) || empty($template_slug)) { throw new InvalidArgumentException('Template slug must be a non empty string'); } $this->template_slug = $template_slug; } /** * @param array $data */ public function set_template_data(array $data) { $this->template_data = $data; } } 

Still doing nothing

And after all this code the shortcode is still amounting to nothing.
I will implement the Smarty adapter next and have the shortcode render on the page.

I appreciate your input